刨根问底HTTP和WebSocket协议,录音的踩坑之旅

2019-09-11 16:05栏目:人才招聘
TAG:

刨根问底HTTP和WebSocket协议

2016/08/17 · 基础技术 · 1 评论 · HTTP, websocket

原文出处: TheAlchemist   

ag真人 1

那天和boss聊天,不经意间提到了Meteor,然后聊到了WebSocket,然后就有了以下对话,不得不说,看问题的方式不同,看到的东西也会大不相同。
A:Meteor是一个很新的开发框架,我觉得它设计得十分巧妙。
B:怎么个巧妙之处?
A:它的前后端全部使用JS,做到了真正的前后端统一;前端浏览器里存有一份后台开放出来的数据库的拷贝,快;使用WebSocket协议来做数据传输协议,来同步前后端的数据库,实现了真正的实时同步。
B:哦?WebSocket是什么东西?真实时?那底层是不是还是轮训?和HTTP的长连接有什么不同?
A:(开始心虚)它是一个新的基于TCP的应用层协议,只需要一次连接,以后的数据不需要重新建立连接,可以直接发送,它是基于TCP的,属于和HTTP相同的地位(呃,开始胡诌了),底层不是轮训,和长连接的区别……这个就不清楚了。
B:它的传输过程大致是什么样子的呢?
A:首先握手连接(又是胡诌),好像可以基于HTTP建立连接(之前用过Socket.io,即兴胡诌),建立了连接之后就可以传输数据了,还包括断掉之后重连等机制。
B:看起来和HTTP长连接做的事情差不多嘛,好像就是一种基于HTTP和Socket的协议啊。
A:呃……(我还是回去看看书吧)

有时候看事情确实太流于表面,了解到了每个事物的大致轮廓,但不求甚解,和朋友聊天说出来也鲜有人会刨根问底,导致了很多基础知识并不牢靠,于是回来大致把HTTP和WebSocket协议的RFC文档(RFC2616 和 RFC6455),刚好对HTTP的传输过程一直有点模糊,这里把两个协议的异同总结一下。

HTML5 录音的踩坑之旅

2017/12/25 · HTML5ag真人, · 录音

原文出处: 翁旺   

视频播放–踩坑小计

2018/06/09 · JavaScript · 视频

原文出处: chenjsh36   

 

随着流量时代的到来和硬件技术的提升,越来越多的网站希望能在PC端或移动端播放自己的视频,而 <video>的兼容性的逐渐完善,使得开发者更愿意使用它来实现视频播放场景。

本篇文章主要罗列__视频播放的通用场景及各场景下踩过的坑__,希望能__帮助开发者在遇到需求开发时能更快地选择合适的技术方案同时减少采坑的次数__。

协议基础

仔细去看这两个协议,其实都非常简单,但任何一个事情想做到完美都会慢慢地变得异常复杂,各种细节。这里只会简单地描述两个协议的结构,并不会深入到很深的细节之处,对于理解http已经足够了。

开篇闲扯

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

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

 

场景一:自动播放

autoPlay 布尔属性;指定后,视频会马上自动开始播放,不会停下来等着数据载入结束。

视频自动播放可以在页面打开且资源加载足够的情况下让视频自动播放,减少一次用户点击的交互,同时可以应用在动效背景、H5仿视频通话的功能。不过由于各种原因,自动播放无论在PC端还是移动端都有不同程度的限制。

HTTP

HTTP的地址格式如下:

JavaScript

http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] 协议和host不分大小写

1
2
http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
协议和host不分大小写

录音前的准备

开始录音前,要先获取当前设备是否支持 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;
}
);

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

移动端

HTTP消息

一个HTTP消息可能是request或者response消息,两种类型的消息都是由开始行(start-line),零个或多个header域,一个表示header域结束的空行(也就是,一个以CRLF为前缀的空行),一个可能为空的消息主体(message-body)。一个合格的HTTP客户端不应该在消息头或者尾添加多余的CRLF,服务端也会忽略这些字符。

header的值不包括任何前导或后续的LWS(线性空白),线性空白可能会出现在域值(filed-value)的第一个非空白字符之前或最后一个非空白字符之后。前导或后续的LWS可能会被移除而不会改变域值的语意。任何出现在filed-content之间的LWS可能会被一个SP(空格)代替。header域的顺序不重要,但建议把常用的header放在前边(协议里这么说的)。

开始录音、暂停录音

这里有个比较特别的点,就是需要添加一个中间变量来标识是否当前是否在录音。因为在火狐浏览器上,我们发现一个问题,录音的流程都是正常的,但是点击暂停时却发现怎么也暂停不了,我们当时是使用 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 为采样率
}

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

IOS

早期必须要有用户手势(user gesture)video标签才可以播放; 从版本10开始修改了video的规则,苹果放宽了inline和autoplay,策略如下(仅适用于Safari浏览器):

  • <video> elements will be allowed to autoplay without a user gesture if their source media contains no audio tracks.(无音频源的 video 元素 允许自动播放)
  • <video muted> elements will also be allowed to autoplay without a user gesture.(禁音的 video 元素允许自动播放)
  • If a <video> element gains an audio track or becomes un-muted without a user gesture, playback will pause.(如果 video 元素在没有用户手势下有了音频源或者变成非禁音,会暂停播放)
  • <video autoplay> elements will only begin playing when visible on-screen such as when they are scrolled into the viewport, made visible through CSS, and inserted into the DOM.(video 元素屏幕可见才开始播放)
  • <video autoplay> elements will pause if they become non-visible, such as by being scrolled out of the viewport.(video元素不可见后停止播放)
Request消息

RFC2616中这样定义HTTP Request 消息:

JavaScript

Request = Request-Line *(( general-header | request-header(跟本次请求相关的一些header) | entity-header ) CRLF)(跟本次请求相关的一些header) CRLF [ message-body ]

1
2
3
4
5
6
Request = Request-Line
          *(( general-header
            | request-header(跟本次请求相关的一些header)
            | entity-header ) CRLF)(跟本次请求相关的一些header)
          CRLF
          [ message-body ]

一个HTTP的request消息以一个请求行开始,从第二行开始是header,接下来是一个空行,表示header结束,最后是消息体。

请求行的定义如下:

JavaScript

//请求行的定义 Request-Line = Method SP Request-URL SP HTTP-Version CRLF //方法的定义 Method = "OPTIONS" | "GET" | "HEAD" |"POST" |"PUT" |"DELETE" |"TRACE" |"CONNECT" | extension-method //资源地址的定义 Request-URI ="*" | absoluteURI | abs_path | authotity(CONNECT)

1
2
3
4
5
6
7
8
//请求行的定义
Request-Line = Method SP Request-URL SP HTTP-Version CRLF
 
//方法的定义
Method = "OPTIONS" | "GET" | "HEAD"  |"POST" |"PUT" |"DELETE" |"TRACE" |"CONNECT"  | extension-method
 
//资源地址的定义
Request-URI   ="*" | absoluteURI | abs_path | authotity(CONNECT)

Request消息中使用的header可以是general-header或者request-header,request-header(后边会讲解)。其中有一个比较特殊的就是Host,Host会与reuqest Uri一起来作为Request消息的接收者判断请求资源的条件,方法如下:

  1. 如果Request-URI是绝对地址(absoluteURI),这时请求里的主机存在于Request-URI里。任何出现在请求里Host头域值应当被忽略。
  2. 假如Request-URI不是绝对地址(absoluteURI),并且请求包括一个Host头域,则主机由该Host头域值决定。
  3. 假如由规则1或规则2定义的主机是一个无效的主机,则应当以一个400(错误请求)错误消息返回。

结束录音

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

安卓

__早期__同样需要用户手势才可以播放; 安卓的 chrome 53 后放宽了自动播放策略,策略不同于IOS的Safari,需要同时对 video 设置 autoplay 和 muted(是否禁音),才允许自动播放; __安卓的 FireFox 和 UC 浏览器__支持任何情况下的自动播放; 安卓的其他浏览器暂时不清楚情况;

Response消息

响应消息跟请求消息几乎一模一样,定义如下:

JavaScript

Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ]

1
2
3
4
5
6
   Response      = Status-Line              
                   *(( general-header        
                    | response-header      
                    | entity-header ) CRLF)  
                   CRLF
                   [ message-body ]

可以看到,除了header不使用request-header之外,只有第一行不同,响应消息的第一行是状态行,其中就包含大名鼎鼎的返回码

Status-Line的内容首先是协议的版本号,然后跟着返回码,最后是解释的内容,它们之间各有一个空格分隔,行的末尾以一个回车换行符作为结束。定义如下:

JavaScript

Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF

1
   Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF

获取频率

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;
};

具体可以参考

PC端

早期是__支持自动播放,但__近来 Safari、Chrome 陆续修改了自动播放的策略……

返回码

返回码是一个3位数,第一位定义的返回码的类别,总共有5个类别,它们是:

JavaScript

- 1xx: Informational - Request received, continuing process - 2xx: Success - The action was successfully received, understood, and accepted

  • 3xx: Redirection - Further action must be taken in order to complete the request - 4xx: Client Error - The request contains bad syntax or cannot be fulfilled - 5xx: Server Error - The server failed to fulfill an apparently valid request
1
2
3
4
5
6
7
8
9
10
11
12
13
  - 1xx: Informational - Request received, continuing process
 
  - 2xx: Success - The action was successfully received,
    understood, and accepted
 
  - 3xx: Redirection - Further action must be taken in order to
    complete the request
 
  - 4xx: Client Error - The request contains bad syntax or cannot
    be fulfilled
 
  - 5xx: Server Error - The server failed to fulfill an apparently
    valid request

RFC2616中接着又给出了一系列返回码的扩展,这些都是我们平时会用到的,但是那些只是示例,HTTP1.1不强制通信各方遵守这些扩展的返回码,通信各方在返回码的实现上只需要遵守以上边定义的这5种类别的定义,意思就是,返回码的第一位要严格按照文档中所述的来,其他的随便定义。

任何人接收到一个不认识的返回码xyz,都可以把它当做x00来对待。对于不认识的返回码的响应消息,不可以缓存。

其它

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

Safari 浏览器

__Safari 10 后__带音频的视频和音频默认禁止自动播放,更多信息可以参考这篇文章;

Chrome(旧版本) 下自动播放:

ag真人 2

Safari (10后)不自动播放:

ag真人 3

Header

RFC2616中定义了4种header类型,在通信各方都认可的情况下,请求头可以被扩展的(可信的扩展只能等到协议的版本更新),如果接收者收到了一个不认识的请求头,这个头将会被当做实体头。4种头类型如下:

  1. 通用头(General Header Fields):可用于request,也可用于response的头,但不可作为实体头,只能作为消息的头。
JavaScript

general-header = Cache-Control ; Section 14.9 | Connection ; Section
14.10 | Date ; Section 14.18 | Pragma ; Section 14.32 | Trailer ;
Section 14.40 | Transfer-Encoding ; Section 14.41 | Upgrade ;
Section 14.42 | Via ; Section 14.45 | Warning ; Section 14.46

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4736f14ed955473721-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14ed955473721-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14ed955473721-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14ed955473721-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14ed955473721-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14ed955473721-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14ed955473721-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14ed955473721-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14ed955473721-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4736f14ed955473721-1" class="crayon-line">
general-header = Cache-Control            ; Section 14.9
</div>
<div id="crayon-5b8f4736f14ed955473721-2" class="crayon-line crayon-striped-line">
              | Connection               ; Section 14.10
</div>
<div id="crayon-5b8f4736f14ed955473721-3" class="crayon-line">
              | Date                     ; Section 14.18
</div>
<div id="crayon-5b8f4736f14ed955473721-4" class="crayon-line crayon-striped-line">
              | Pragma                   ; Section 14.32
</div>
<div id="crayon-5b8f4736f14ed955473721-5" class="crayon-line">
              | Trailer                  ; Section 14.40
</div>
<div id="crayon-5b8f4736f14ed955473721-6" class="crayon-line crayon-striped-line">
              | Transfer-Encoding        ; Section 14.41
</div>
<div id="crayon-5b8f4736f14ed955473721-7" class="crayon-line">
              | Upgrade                  ; Section 14.42
</div>
<div id="crayon-5b8f4736f14ed955473721-8" class="crayon-line crayon-striped-line">
              | Via                      ; Section 14.45
</div>
<div id="crayon-5b8f4736f14ed955473721-9" class="crayon-line">
              | Warning                  ; Section 14.46
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 请求头(Request Header Fields):被请求发起端用来改变请求行为的头。
JavaScript

request-header = Accept ; Section 14.1 | Accept-Charset ; Section
14.2 | Accept-Encoding ; Section 14.3 | Accept-Language ; Section
14.4 | Authorization ; Section 14.8 | Expect ; Section 14.20 | From
; Section 14.22 | Host ; Section 14.23 | If-Match ; Section 14.24 |
If-Modified-Since ; Section 14.25 | If-None-Match ; Section 14.26 |
If-Range ; Section 14.27 | If-Unmodified-Since ; Section 14.28 |
Max-Forwards ; Section 14.31 | Proxy-Authorization ; Section 14.34 |
Range ; Section 14.35 | Referer ; Section 14.36 | TE ; Section 14.39
| User-Agent ; Section 14.43

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-15">
15
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-16">
16
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-17">
17
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f0425423013-18">
18
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f0425423013-19">
19
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4736f14f0425423013-1" class="crayon-line">
request-header = Accept                   ; Section 14.1
</div>
<div id="crayon-5b8f4736f14f0425423013-2" class="crayon-line crayon-striped-line">
               | Accept-Charset           ; Section 14.2
</div>
<div id="crayon-5b8f4736f14f0425423013-3" class="crayon-line">
               | Accept-Encoding          ; Section 14.3
</div>
<div id="crayon-5b8f4736f14f0425423013-4" class="crayon-line crayon-striped-line">
               | Accept-Language          ; Section 14.4
</div>
<div id="crayon-5b8f4736f14f0425423013-5" class="crayon-line">
               | Authorization            ; Section 14.8
</div>
<div id="crayon-5b8f4736f14f0425423013-6" class="crayon-line crayon-striped-line">
               | Expect                   ; Section 14.20
</div>
<div id="crayon-5b8f4736f14f0425423013-7" class="crayon-line">
               | From                     ; Section 14.22
</div>
<div id="crayon-5b8f4736f14f0425423013-8" class="crayon-line crayon-striped-line">
               | Host                     ; Section 14.23
</div>
<div id="crayon-5b8f4736f14f0425423013-9" class="crayon-line">
               | If-Match                 ; Section 14.24
</div>
<div id="crayon-5b8f4736f14f0425423013-10" class="crayon-line crayon-striped-line">
               | If-Modified-Since        ; Section 14.25
</div>
<div id="crayon-5b8f4736f14f0425423013-11" class="crayon-line">
               | If-None-Match            ; Section 14.26
</div>
<div id="crayon-5b8f4736f14f0425423013-12" class="crayon-line crayon-striped-line">
               | If-Range                 ; Section 14.27
</div>
<div id="crayon-5b8f4736f14f0425423013-13" class="crayon-line">
               | If-Unmodified-Since      ; Section 14.28
</div>
<div id="crayon-5b8f4736f14f0425423013-14" class="crayon-line crayon-striped-line">
               | Max-Forwards             ; Section 14.31
</div>
<div id="crayon-5b8f4736f14f0425423013-15" class="crayon-line">
               | Proxy-Authorization      ; Section 14.34
</div>
<div id="crayon-5b8f4736f14f0425423013-16" class="crayon-line crayon-striped-line">
               | Range                    ; Section 14.35
</div>
<div id="crayon-5b8f4736f14f0425423013-17" class="crayon-line">
               | Referer                  ; Section 14.36
</div>
<div id="crayon-5b8f4736f14f0425423013-18" class="crayon-line crayon-striped-line">
               | TE                       ; Section 14.39
</div>
<div id="crayon-5b8f4736f14f0425423013-19" class="crayon-line">
               | User-Agent               ; Section 14.43
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 响应头(Response Header Fields):被服务器用来对资源进行进一步的说明。
JavaScript

response-header = Accept-Ranges ; Section 14.5 | Age ; Section 14.6
| ETag ; Section 14.19 | Location ; Section 14.30 |
Proxy-Authenticate ; Section 14.33 | Retry-After ; Section 14.37 |
Server ; Section 14.38 | Vary ; Section 14.44 | WWW-Authenticate ;
Section 14.47

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4736f14f4393113224-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f4393113224-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f4393113224-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f4393113224-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f4393113224-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f4393113224-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f4393113224-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f4393113224-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f4393113224-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4736f14f4393113224-1" class="crayon-line">
response-header = Accept-Ranges           ; Section 14.5
</div>
<div id="crayon-5b8f4736f14f4393113224-2" class="crayon-line crayon-striped-line">
                | Age                     ; Section 14.6
</div>
<div id="crayon-5b8f4736f14f4393113224-3" class="crayon-line">
                | ETag                    ; Section 14.19
</div>
<div id="crayon-5b8f4736f14f4393113224-4" class="crayon-line crayon-striped-line">
                | Location                ; Section 14.30
</div>
<div id="crayon-5b8f4736f14f4393113224-5" class="crayon-line">
                | Proxy-Authenticate      ; Section 14.33
</div>
<div id="crayon-5b8f4736f14f4393113224-6" class="crayon-line crayon-striped-line">
                | Retry-After             ; Section 14.37
</div>
<div id="crayon-5b8f4736f14f4393113224-7" class="crayon-line">
                | Server                  ; Section 14.38
</div>
<div id="crayon-5b8f4736f14f4393113224-8" class="crayon-line crayon-striped-line">
                | Vary                    ; Section 14.44
</div>
<div id="crayon-5b8f4736f14f4393113224-9" class="crayon-line">
                | WWW-Authenticate        ; Section 14.47
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 实体头(Entity Header Fields):如果消息带有消息体,实体头用来作为元信息;如果没有消息体,就是为了描述请求的资源的信息。
JavaScript

entity-header = Allow ; Section 14.7 | Content-Encoding ; Section
14.11 | Content-Language ; Section 14.12 | Content-Length ; Section
14.13 | Content-Location ; Section 14.14 | Content-MD5 ; Section
14.15 | Content-Range ; Section 14.16 | Content-Type ; Section 14.17
| Expires ; Section 14.21 | Last-Modified ; Section 14.29 |
extension-header

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f7627741631-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f7627741631-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f7627741631-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f7627741631-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4736f14f7627741631-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f4736f14f7627741631-11">
11
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4736f14f7627741631-1" class="crayon-line">
entity-header  = Allow                    ; Section 14.7
</div>
<div id="crayon-5b8f4736f14f7627741631-2" class="crayon-line crayon-striped-line">
               | Content-Encoding         ; Section 14.11
</div>
<div id="crayon-5b8f4736f14f7627741631-3" class="crayon-line">
               | Content-Language         ; Section 14.12
</div>
<div id="crayon-5b8f4736f14f7627741631-4" class="crayon-line crayon-striped-line">
               | Content-Length           ; Section 14.13
</div>
<div id="crayon-5b8f4736f14f7627741631-5" class="crayon-line">
               | Content-Location         ; Section 14.14
</div>
<div id="crayon-5b8f4736f14f7627741631-6" class="crayon-line crayon-striped-line">
               | Content-MD5              ; Section 14.15
</div>
<div id="crayon-5b8f4736f14f7627741631-7" class="crayon-line">
               | Content-Range            ; Section 14.16
</div>
<div id="crayon-5b8f4736f14f7627741631-8" class="crayon-line crayon-striped-line">
               | Content-Type             ; Section 14.17
</div>
<div id="crayon-5b8f4736f14f7627741631-9" class="crayon-line">
               | Expires                  ; Section 14.21
</div>
<div id="crayon-5b8f4736f14f7627741631-10" class="crayon-line crayon-striped-line">
               | Last-Modified            ; Section 14.29
</div>
<div id="crayon-5b8f4736f14f7627741631-11" class="crayon-line">
               | extension-header
</div>
</div></td>
</tr>
</tbody>
</table>

结语

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

1 赞 3 收藏 评论

ag真人 4

Chrome 浏览器

禁音的视频依旧可以播放,�带声音的视频会根据__媒体参与指数__来决定能否自动播放,那什么是媒体参与指数?官方给了解释和相关的维度:

MEI 是一个评估用户对于当前站点的媒体参与程度的指数,它取决于下面几个维度:

  • 用户在媒体上停留时间超过了 7秒以上
  • 音频必须是展示出来,并且没有静音
  • 与 video 之间有过交互
  • 媒体的尺寸不小于 200×140.

看完后开发者的心里是这样的:

ag真人 5

ag真人 6

消息体(Message Body)和实体主体(Entity Body)

如果有Transfer-Encoding头,那么消息体解码完了就是实体主体,如果没有Transfer-Encoding头,消息体就是实体主体。

JavaScript

message-body = entity-body | <entity-body encoded as per Transfer-Encoding>

1
2
   message-body = entity-body
                | <entity-body encoded as per Transfer-Encoding>

在request消息中,消息头中含有Content-Length或者Transfer-Encoding,标识会有一个消息体跟在后边。如果请求的方法不应该含有消息体(如OPTION),那么request消息一定不能含有消息体,即使客户端发送过去,服务器也不会读取消息体。

在response消息中,是否存在消息体由请求方法和返回码来共同决定。像1xx,204,304不会带有消息体。

检测能否自动播放?

好在无论是 Safari 还是 Chrome,在限制了自动播放的同时,提供了检测视频是否能自动播放的机制,以便于开发者在发现无法自动播放时有备选方案:

var promise = document.querySelector('video').play(); if (promise !== undefined) { promise.catch(error => { // Auto-play was prevented // Show a UI element to let the user manually start playback }).then(() => { // Auto-play started }); }

1
2
3
4
5
6
7
8
9
10
var promise = document.querySelector('video').play();
 
if (promise !== undefined) {
    promise.catch(error => {
        // Auto-play was prevented
        // Show a UI element to let the user manually start playback
    }).then(() => {
        // Auto-play started
    });
}

版权声明:本文由ag真人发布于人才招聘,转载请注明出处:刨根问底HTTP和WebSocket协议,录音的踩坑之旅