flv.js 1.0 源码学习(二)

二、IO

io-controller.js-IOController类-IO控制器

IOController属性
  1. 属性分为基本信息、暂存区信息、加载器信息、数据信息、速率信息、IO 状态信息、事件处理器

    • 1.1 基本信息 TAG 用来描述当前构造器名称、_config 接收用户自定义配置、_extraData 是提取的数据(这块1.0 版本只在转码控制器里用了)
      • 1.1.1 _extraData 作为读写属性 extraData 暴露
    • 1.2 暂存区,_stashInitialSize 是初始大小,如果非实时流 384K,否则512K,_stashUsed 是已使用空间,_stashSize 是真实大小,还有 _bufferSize 3M 缓存区大小,对应的还有 _stashBuffer 暂存缓存和 _stashByteStart 暂存起点,可以通过 config.enableStashBuffer 控制 _enableStash 是否开启暂存
    • 1.3 加载器信息只有 _loader 加载器实例、_loaderClass 加载器类型、_seekHandler 搜索处理器
      • 1.3.1 _loader.status 关联实例只读属性 status
      • 1.3.2 _loader.currentSpeed 关联实例读写属性 currentSpeed
      • 1.3.3 _loader.type 关联实例读写属性 loaderType
    • 1.4 数据信息有 _dataSource 原始数据源、_isWebSocketURL 是否是 ws 协议、_refTotalLength 原数据大小、_totalLength 数据总长度、_fullRequestFlag 请求全部标志位、_currentRange 当前的数据范围
      • 1.4.1 _dataSource.currentUrl 关联实例只读属性 currentUrl
    • 1.5 速率信息有 _speed 速率、_speedNormalized 标准速率、_speedSampler 速率计算器、_speedNormalizeList 常规速率表
    • 1.6 IO 状态信息有 _isEarlyEofReconnecting 是否过早结束、_paused 是否暂停、_resumeFrom 恢复点
    • 1.7 事件处理器有 _onDataArrival 数据抵达、_onSeeked 搜索、_onError 出错、_onComplete 完成、_onRecoveredEarlyEof 过早结束
      • 1.7.1 这五个属性都有相应的实例读写属性
  2. 构造函数里执行了选择搜索处理函数、选择加载器、创建加载器的操作


    IOController方法
  3. 外部方法分为获取状态的、控制状态的、操作数据的

    • 3.1 获取状态的有 isWorking 和 isPaused
      • 3.1.1 isPaused() 其实就是返回 this._paused 的值
      • 3.1.2 isWorking() 有三点要求:当前实例存在加载器、当前实例加载器正常运行、当前实例没有暂停
    • 3.2 控制状态的有 open、pause、resume、abort、destroy
      • 3.2.1 open(optionalFrom) 用来从一个起点开始加载数据
        • 3.2.1.1 设置当前实例的当前范围为 0 到 -1
        • 3.2.1.2 将实例的当前范围起点设置为传入的起点,如果未传入参数,设置请求全部标志位为真
        • 3.2.1.3 重置当前实例的速率计算器
        • 3.2.1.4 打开当前实例的加载器
      • 3.2.2 pause() 用来暂停加载数据
        • 3.2.2.1 如果当前实例处于工作中// 没写否则的情况啊,有点简略
        • 3.2.2.2 强行终止当前实例的加载器
        • 3.2.2.3 如果当前实例的已使用暂存区不为空
          • 3.2.2.3.1 将实例的恢复点设置为暂存区起点
          • 3.2.2.3.2 将实例的当前范围的结尾设置为为暂存区起点 - 1
        • 3.2.2.3 否则,将当前实例的恢复点设置为当前范围的结尾 + 1
        • 3.2.2.4 设置实例的已使用暂存区为空
        • 3.2.2.5 设置实例的暂存区起点为 0
        • 3.2.2.6 设置实例的暂停属性为 true
      • 3.2.3 resume() 用来恢复暂停加载的实例
        • 3.2.3.1 如果当前实例处于暂停中
        • 3.2.3.2 设置实例的暂停属性为 false
        • 3.2.3.3 将实例的恢复点存储到一个 bytes 变量里,然后设为 0
        • 3.2.3.4 将 bytes 传入内部搜索方法中
      • 3.2.4 abort() 用来终止加载
        • 3.2.4.1 终止当前实例的加载器
        • 3.2.4.2 如果实例是暂停的,将暂停属性设为 false,并将恢复点设置为 0
      • 3.2.5 destroy() 用来销毁当前实例
        • 3.2.5.1 如果当前实例的加载器处于工作中,终止此加载器
        • 3.2.5.2 销毁当前实例的加载器,清空构造函数初始化的哪些变量


          3.2.5.2
    • 3.3 操作数据的有 seek、updateUrl
      • 3.3.1 seek(bytes) 用来搜索特定的一段数据
        • 3.3.1.1 设置实例的暂停属性为 false
        • 3.3.1.2 设置实例的已用暂存区为 0
        • 3.3.1.3 设置实例的暂存区起点为 0
        • 3.3.1.4 将 bytes 传入内部搜索方法中
      • 3.3.2 updateUrl(url) 用来更新数据源的 URL
        • 3.3.2.1 这个方法还没完善
  4. 内部方法分为数据有关、操作加载器、操作暂存区、事件处理

    • 4.1 数据有关的有 _selectSeekHandler、_internalSeek、_normalizeSpeed、_dispatchChunks
      • 4.1.1 _selectSeekHandler() 用来选择搜索处理函数
        • 4.1.1.1 根据实例的配置的搜索类型,从三种:range、param、custom 处理函数选择一个
        • 4.1.1.2 如果搜索类型是 range,实例化一个 RangeSeekHandler 赋给实例的搜索处理函数,传入实例配置的范围是否从零开始属性
        • 4.1.1.2 如果搜索类型是 param
          • 4.1.1.2.1 初始化参数起点,为实例配置的参数起点值或 bstart
          • 4.1.1.2.2 初始化参数终点,为实例配置的参数终点值或 bend
          • 4.1.1.2.3 实例化一个 ParamSeekHandler 赋给实例的搜索处理函数,传入参数起点和终点
        • 4.1.1.3 如果搜索类型是 custom
          • 4.1.1.3.1 如果实例配置的自定义搜索处理函数无效,报错
          • 4.1.1.3.2 实例化一个自定义搜索处理函数实例 赋给实例的搜索处理函数
        • 4.1.1.4 否则报不合法的参数错
      • 4.1.2 _internalSeek(bytes, dropUnconsumed) 用来从特定时间点加载数据
        • 4.1.2.1 如果实例的加载器还在工作,终止加载器
        • 4.1.2.2 丢弃未消费数据地刷新暂存区
        • 4.1.2.3 销毁实例的加载器
        • 4.1.2.4 新建一个请求范围,起点是传入的 bytes,终点是 -1
        • 4.1.2.5 设置实例的当前数据范围,起点是请求范围的起点,终点是 -1
        • 4.1.2.6 清空实例的速率、速率计算器
        • 4.1.2.7 初始化实例的暂存区大小
        • 4.1.2.8 创建一个新的加载器
        • 4.1.2.9 调用新加载器的 open 方法,传入数据源和请求范围
        • 4.1.2.10 如果实例对搜索事件有处理函数,调用之
      • 4.1.3 _normalizeSpeed(input) 用来确定加载速度,使用了二分法,类似滑动窗口思想
        • 4.1.3.1 获取常规速率表,采用二分法确认输入参数 input 应该在表中的哪个位置
        • 4.1.3.2 返回常规速率表那个位置的值(比实际值偏小)
      • 4.1.4 _dispatchChunks(chunks, byteStart) 是分派加载完数据的函数,参数为数据块和数据起点
        • 4.1.4.1 将实例的当前范围的终点设置为数据起点 + 数据块大小 - 1
        • 4.1.4.2 调用实例的数据到达事件处理函数
    • 4.2 操作加载器的有 _selectLoader、_createLoader
      • 4.2.1 _selectLoader() 用来选择加载器
        • 4.2.1.1 如果实例有 ws 协议,设置实例的加载器类型为 WebSocketLoader
        • 4.2.1.1 如果支持 fetch 流,设置实例的加载器类型为 FetchStreamLoader
        • 4.2.1.1 如果支持火狐,设置实例的加载器类型为 MozChunkedLoader
        • 4.2.1.1 如果支持通用加载器,设置实例的加载器类型为 RangeLoader
        • 4.2.1.1 否则,报运行时的浏览器不支持二进制响应数据错
      • 4.2.2 _createLoader() 用来创建加载器 我觉得这里 bind 多余了
        • 4.2.2.1 根据加载器类型,将搜索处理器作为参数,实例化一个加载器,赋给实例的加载器
        • 4.2.2.2 如果实例的加载器的需要暂存缓存为 false,就将实例的是否开启暂存设置为 false
        • 4.2.2.3 将实例的加载器的事件处理函数和实例的事件处理函数关联起来


          4.2.2.3
  • 4.3 操作暂存区的有 _expandBuffer、_adjustStashSize、_flushStashBuffer
    • 4.3.1 _expandBuffer(expectedBytes) 用来扩展缓存,借鉴了滑动窗口思想
      • 4.3.1.1 创建新缓存大小,初始值为暂存区大小
      • 4.3.1.2 只要新缓存大小 + 1M 小于 expectedBytes,就将新缓存大小翻倍
      • 4.3.1.3 给新缓存大小加 1M
      • 4.3.1.4 如果新缓存大小等于实例缓存大小,结束
      • 4.3.1.5 创建新缓存,初始化为新缓存大小那么大的一个二进制数组
      • 4.3.1.6 如果实例的已使用的暂存区不为空
        • 4.3.1.6.1 创建一个老暂存二进制数组,类型为 Uint8Array,指向实例的暂存区缓存,开始于字节0,长度为实例的已使用的暂存区大小
        • 4.3.1.6.2 创建一个新暂存二进制数组,类型为 Uint8Array,指向新缓存,开始于字节0,长度为新缓存大小
        • 4.3.1.6.3 拷贝老暂存数组到新暂存数组
      • 4.3.1.7 将实例的暂存区缓存设置为新缓存
      • 4.3.1.8 将实例的缓存大小设置为新缓存大小
    • 4.3.2 _adjustStashSize(normalized) 用来调整暂存区大小
      • 4.3.2.1 创建 KB 级的暂存区大小,初始值为 0
      • 4.3.2.2 如果实例配置的实时流属性为 true
        • 4.3.2.2.1 将 KB 级的暂存区大小设置为 normalized
      • 4.3.2.2 否则
        • 4.3.2.2.1 如果 normalized 小于 512,将 KB 级的暂存区大小设置为 normalized
        • 4.3.2.2.1 如果 normalized 在 [512,1024] 中,将 KB 级的暂存区大小设置为 normalized 的 - 1.5 倍
        • 4.3.2.2.1 否则,将 KB 级的暂存区大小设置为 normalized 的 2 倍
      • 4.3.2.3 如果 KB 级的暂存区大小大于 8192,就校正 KB 级的暂存区大小为 8192
      • 4.3.2.4 创建缓存大小,初始值为暂存区大小 + 1M
      • 4.3.2.5 如果实例的缓存大小小于缓存大小,给实例缓存扩展缓存大小那么多
      • 4.3.2.6 将实例的暂存区大小设置为暂存区大小
    • 4.3.3 _flushStashBuffer(dropUnconsumed) 用来刷新暂存区和缓存
      • 4.3.3.1 如果实例的已使用缓存区不为空
        • 4.3.3.1.1 创建缓存,初始化为实例的暂存区缓存从 0 到已使用缓存大小
        • 4.3.3.1.2 创建已消费长度,初始化为加载完缓存(从实例的暂存起点)后的处理函数返回的结果
        • 4.3.3.1.3 创建剩余长度,初始化为缓存占据的内存字节长度 - 已消费长度
        • 4.3.3.1.4 如果已消费长度小于缓存占据的内存字节长度
          • 4.3.3.1.4.1 如果要丢弃未消费数据(dropUnconsumed 为 true),打印一句日志告诉用户丢弃了多少剩余长度的数据
          • 4.3.3.1.4.1 否则
            • 4.3.3.1.4.1.1 如果已消费长度大于 0
              • 4.3.1.4.1.1.1 创建暂存二进制数组,Uint8Array 类型,指向实例的暂存区缓存,开始于字节0,长度为实例的缓存大小
              • 4.3.1.4.1.1.2 创建剩余二进制数组,Uint8Array 类型,指向实例的暂存区缓存,开始于已消费长度
              • 4.3.1.4.1.1.3 拷贝剩余数组到暂存区数据中
              • 4.3.1.4.1.1.4 设置实例的已使用暂存区大小为剩余数组的内存字节长度
              • 4.3.1.4.1.1.5 给实例的暂存起点增加已消费长度
            • 4.3.3.1.4.1.2 返回 0
        • 4.3.3.1.5 设置实例的已使用暂存区大小为 0
        • 4.3.3.1.6 设置实例的暂存区起点为 0
        • 4.3.3.1.7 返回剩余长度
      • 4.3.3.1 否则,返回 0
  • 4.4 事件处理有 _onContentLengthKnown、_onLoaderChunkArrival、_onLoaderComplete、_onLoaderError
    • 4.4.1 _onContentLengthKnown(contentLength) 处理已知内容长度事件
      • 4.4.1.1 如果 contentLength 存在并且实例的全请求标志为真
        • 4.4.1.1.1 设置实例的数据总长度为 contentLength
        • 4.4.1.1.2 设置实例的全请求标志为假
    • 4.4.2 _onLoaderChunkArrival(chunk, byteStart, receivedLength) 处理数据到达事件
      • 4.4.2.1 如果实例没有处理数据到达事件的函数,报错
      • 4.4.2.2 如果实例的暂停属性为 true,结束
      • 4.4.2.3 如果实例的过早结束属性为 true,将之设置为 false。如果实例存在接收过早结束函数,调用之。
      • 4.4.2.4 实例的速率计算器增加 chunk 字节长度那么多的字节
      • 4.4.2.5 根据速率动态调整存储缓冲区大小


        4.4.2.5
      • 4.4.2.6 如果实例的允许暂存区为 false
        • 4.4.2.6.1 如果实例的已使用暂存区为空
          • 4.4.2.6.1.1 直接将数据分派给消费者
          • 4.4.2.6.1.2 如果已消费数据长度小于 chunk 字节长度
            • 4.4.2.6.1.2.1 创建未消费长度,初始值为 chunk 字节长度 - 已消费长度
            • 4.4.2.6.1.2.2 如果未消费长度大于实例的缓存大小,扩展实例缓存
            • 4.4.2.6.1.2.3 创建暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为实例的缓存大小
            • 4.4.2.6.1.2.4 创建已消费数组,Uint8Array 类型,指向 chunk,起点为已消费长度,拷贝到暂存区数组里去
            • 4.4.2.6.1.2.5 将实例的已使用暂存区长度加上剩余长度
            • 4.4.2.6.1.2.6 设置暂存区起点为 byteStart + 已消费长度
        • 4.4.2.6.1 否则将 chunk 合并到暂存区缓存中,并将暂存区缓存分派给使用者。
          • 4.4.2.6.1.1 如果实例的已使用暂存区大小 + chunk 字节长度 > 实例的缓存大小,扩展实例缓存
          • 4.4.2.6.1.2 创建暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为实例的缓存大小
          • 4.4.2.6.1.3 创建 chunk 数组,Uint8Array 类型,指向 chunk ,拷贝到暂存区数组里去
          • 4.4.2.6.1.4 将实例的已使用暂存区长度加上 chunk 字节长度
          • 4.4.2.6.1.5 创建已消费长度,初始化为实例暂存缓存从 0 到实例已使用暂存区长度那么多,分派之
          • 4.4.2.6.1.6 如果已消费长度小于实例已使用暂存区长度且已消费长度 > 0
            • 4.4.2.6.1.6.1 新建剩余数组,Uint8Array 类型,指向实例的暂存缓存,起点为已消费长度
            • 4.4.2.6.1.6.2 拷贝剩余数组到暂存数组去
          • 4.4.2.6.1.7 将实例的已使用暂存长度减去已消费长度
          • 4.4.2.6.1.8 将暂存区起点加上已消费长度
      • 4.4.2.6 否则
        • 4.4.2.6.1 如果实例的已用暂存区为空且实例的暂存区起点为 0,将实例的暂存区设为 byteStart
        • 4.4.2.6.2 如果实例的已用暂存区长度 + chunk 参数字节长度 <= 实例的暂存区长度
          • 4.4.2.6.2.1 创建暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为实例的暂存区大小
          • 4.4.2.6.2.2 新建 chunk 数组,Uint8Array 类型,指向 chunk 参数,起点为已用暂存区长度,拷贝到暂存区数组去
          • 4.4.2.6.2.3 将实例的已用暂存长度加上 chunk 参数的字节长度
        • 4.4.2.6.2 否则,chunk 太大了,发送整个暂存区缓存,并保留数据,然后将块添加到暂存区缓存
          • 4.4.2.6.2.1 创建暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为缓存大小
          • 4.4.2.6.2.2 如果实例的已用暂存不为空
            • 4.4.2.6.2.2.1 创建缓存,初始值为实例暂存缓存从 0 到实例的已用暂存长度这么多
            • 4.4.2.6.2.2.2 创建已消费长度,初始值为从实例暂存起点分发缓存这么多
            • 4.4.2.6.2.2.3 如果已消费长度 < 缓存字节长度
              • 4.4.2.6.2.2.3.1 如果已消费长度 > 0
                • 4.4.2.6.2.2.3.1.1 创建剩余数组,Uint8Array 类型,指向缓存,起点为已消费长度,拷贝到暂存区数组去
                • 4.4.2.6.2.2.3.1.2 将实例已用暂存长度设为剩余数组字节长度
                • 4.4.2.6.2.2.3.1.3 将暂存起点加上消费长度
              • 4.4.2.6.2.2.3.1 否则,将实例已用暂存长度设为 0,将暂存起点加上消费长度
            • 4.4.2.6.2.2.4 如果实例已用暂存长度 + chunk 字节长度 > 实例缓存大小
              • 4.4.2.6.2.2.4.1 扩展缓存
              • 4.4.2.6.2.2.4.2 重设暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为缓存大小
            • 4.4.2.6.2.2.5 创建 chunk 数组,Uint8Array 类型,指向 chunk ,起点为实例已用暂存长度,拷贝到暂存区数组去
            • 4.4.2.6.2.2.6 将实例已用暂存长度加上 chunk 的字节长度
          • 4.4.2.6.2.2 否则,存储缓冲区为空,但是 chunk 大小 > 暂存区大小,直接派发块并保留数据
            • 4.4.2.6.2.2.1 创建已消费长度,初始值为从 byteStart 分发 chunk 这么多的数据
            • 4.4.2.6.2.2.2 如果已消费长度 < chunk 字节长度
              • 4.4.2.6.2.2.2.1 创建剩余长度,初始值为 chunk 字节长度 - 已消费长度
              • 4.4.2.6.2.2.2.2 如果剩余长度 > 实例缓存大小,扩展缓存,并创建暂存区数组,Uint8Array 类型,指向实例的暂存区缓存,起点为 0,长度为缓存大小
              • 4.4.2.6.2.2.2.3 创建 chunk 数组,Uint8Array 类型,指向 chunk ,起点为已消费长度,拷贝到暂存区数组去
              • 4.4.2.6.2.2.2.4 将实例已使用暂存区长度加上剩余长度
              • 4.4.2.6.2.2.2.5 将实例暂存区起点设为 byteStart + 消费长度
    • 4.4.3 _onLoaderComplete(from, to) 处理数据加载完成事件
      • 4.4.3.1 以丢弃未消费数据模式刷新暂存区和缓存
      • 4.4.3.2 如果实例的完成事件有监听函数,调用之,传入实例的提取数据
    • 4.4.4 _onLoaderError(type, data) 处理数据加载错误事件
      • 4.4.4.1 以保留未消费数据模式刷新暂存区和缓存
      • 4.4.4.2 如果实例的过早结束属性为 true
        • 4.4.4.2.1 设置实例的过早结束属性为 false
        • 4.4.4.2.2 设置 type 为加载器错误的接收过早结束
      • 4.4.4.3 判断 type
        • 4.4.4.3.1 type 是过早结束
          • 4.4.4.3.1.1 如果实例配置的实时流属性为 false
            • 4.4.4.3.1.1.1 如果实例的数据总长度存在
              • 4.4.4.3.1.1.1.1 创建下一个起点,初始化为实例的当前范围的终点 + 1
              • 4.4.4.3.1.1.1.2 如果下一个起点小于实例的数据总长度,设置实例的过早结束属性为 true,调用不丢弃未消费数据的内部搜索方法,传入下一个起点
              • 4.4.4.3.1.1.1.3 结束
          • 4.4.4.3.1.2 设置 type 为加载器错误的接收过早结束
        • 4.4.4.3.1 type 是接收过早结束、连接超时、HTTP 状态码无效、其他:跳过
      • 4.4.4.4 如果实例的错误事件有监听函数,调用之,传入 type 和 data
      • 4.4.4.4 否则报 IOException 错误

疑问:缓存区和暂存区有啥关系?为什么不把暂存区的属性方法单独抽象个类?

range-seek-handler.js-RangeSeekHandler类-range法查找处理器

默认搜索处理函数。


RangeSeekHandler
  1. 属性:_zeroStart,搜索范围是否从 0 开始
  2. 方法:getConfig 获取搜索配置,传入 url 和 range 参数
    • 2.1 新建一个 headers 对象,初始化为 {}
    • 2.2 如果 range 的起点不为 0 或者终点不为 -1
      • 2.2.1 如果 range 的终点不为 -1,headers['Range'] 为 range 的起点到 range 的终点
      • 2.2.1 否则,headers['Range'] 只考虑 range 的起点
    • 2.2 如果搜索范围需要从 0 开始,headers['Range'] 考虑起点为 0
    • 2.3 返回一个对象,键分别为 url 和 headers

操作 headers

param-seek-handler.js-ParamSeekHandler类-param法查找处理器

我猜这种是针对B站特有的一种搜索函数。


ParamSeekHandler
  1. 属性:_startName 搜索范围起点,_endName 搜索范围终点
  2. 方法:getConfig 获取搜索配置,传入 baseUrl 和 range 参数
    • 2.1 新建一个 headers 对象,初始化为 {}
    • 2.2 如果 range 的起点不为 0 或者终点不为 -1
      • 2.2.1 如果 url 没有 ?,url 加上 ?
      • 2.2.1 否则 url 加上 &
      • 2.2.2 url 拼接 实例的起点=range起点
      • 2.2.3 如果 range 终点不等于 -1,url 拼接实例的终点=range终点
      • 2.2.4 返回一个对象,键分别为 url 和 headers

操作 url

speed-sampler.js-SpeedSampler类-实时网速计算器

SpeedSampler
  1. 属性有 _firstCheckpoint 首次检查点、_lastCheckpoint 结尾检查点、_intervalBytes 间隔字节数、_totalBytes 总字节数、_lastSecondBytes 最后一秒字节数、_now 获取当前时间函数(默认 performance.now,替补 Date.now)

  2. 只读属性:

    • 2.1 get currentKBps() 当前速率,值为 1000 * 1024 * 间隔字节数/(当前时间 - 结尾检查点),但是为了防止大数溢出,还是(间隔字节数 /(当前时间 - 结尾检查点)/ 1000 )/ 1024
    • 2.2 get lastSecondKBps() 最后速率
      • 2.2.1 如果实例的最后一秒字节数不为 0,返回实例最后一秒字节数 / 1024
      • 2.2.1 否则
        • 2.2.1.1 如果实例当前时间 - 实例结尾检查点 >= 500,返回实例当前速率
        • 2.2.1.1 否则,返回 0
    • 2.3 get averageKBps() 平均速率,值为(总字节数 /(当前时间 - 首次检查点)/ 1000 )/ 1024

这里要说一句,时间算完要除 1000,单位就从毫秒变成了秒,再套用经典公式 v = sum / time,就得到了网络传输速率,只不过单位是字节,一般我们是以 K 表示,所以还要除 1024

  1. 方法
    • 3.1 reset() 重置,其实就是让五个实例属性值全变成 0
    • 3.2 addBytes(bytes) 增加字节数,传入 bytes 参数
      • 3.2.1 如果实例首次检查点为 0
        • 3.2.1.1 将实例首次检查点设为现在
        • 3.2.1.2 将实例结尾检查点设为实例首次检查点
        • 3.2.1.3 将实例间隔字节数加上 bytes
        • 3.2.1.4 将实例总字节数加上 bytes
      • 3.2.1 如果当前时间 - 实例结尾检查点 < 1000
        • 3.2.1.1 将实例间隔字节数加上 bytes
        • 3.2.1.2 将实例总字节数加上 bytes
      • 3.2.1 否则
        • 3.2.1.1 将实例最后一秒字节数设为实例间隔字节数
        • 3.2.1.2 将实例间隔字节数设为 bytes
        • 3.2.1.3 将实例总字节数加上 bytes
        • 3.2.1.4 将实例结尾检查点设为现在

这个类最关键的方法就是 addBytes,因为在设立初始化检查点后,每次添加字节,都会更新结尾检查点,然后计算一次速率。所以,它是一段一段算速率的。

loader.js

  1. BaseLoader类-数据加载器


    BaseLoader
    • 1.1 属性有 _type 加载器类型、_status 加载器状态、_needStash 是否需要暂存区、_onContentLengthKnown 已知内容长度事件处理函数、_onDataArrival 数据抵达事件处理函数、_onError 出错事件处理函数、_onComplete 完成事件处理函数
      • 1.1.1 加载器类型初始化为构造函数传入的 typeName 参数,并且有对应的实例只读属性 type
      • 1.1.2 加载器状态初始化为闲置状态,并且有对应的实例只读属性 status
      • 1.1.3 是否需要暂存区初始化为 false,并且有对应的实例只读属性 needStash
      • 1.1.4 四个事件处理函数初始化为 null,对应四个实例属性
    • 1.2 方法有
      • 1.2.1. destroy() 销毁加载器实例,就是将实例状态设为重置,四个事件处理函数设置为 null
      • 1.2.2. isWorking() 获取加载器运行状态,根据加载器状态是连接中或者缓存中来判断
      • 1.2.3. open(dataSource, range) 打开数据源开始加载,报错---延迟到子类实现
      • 1.2.4. abort() 终止加载,报错---延迟到子类实现
  2. LoaderStatus常量-加载器状态

    • 2.1 kIdle 闲置
    • 2.2 kConnecting 连接中
    • 2.3 kBuffering 缓冲中
    • 2.4 kError 出错
    • 2.5 kComplete 完成
  3. LoaderErrors-加载器错误

    • 3.1 OK 成功
    • 3.2 EXCEPTION 其他错误
    • 3.3 HTTP_STATUS_CODE_INVALID HTTP 状态码错误
    • 3.4 CONNECTING_TIMEOUT 连接超时
    • 3.5 EARLY_EOF 过早结束
    • 3.6 UNRECOVERABLE_EARLY_EOF 不可恢复的过早结束

fetch-stream-loader.js-FetchStreamLoader类-fetch加载器

FetchStreamLoader
  1. 属性

    • 1.1 isSupported() 是否支持 fetch 流加载:浏览器非 IE、全局有 fetch 方法和 ReadableStream 属性
    • 1.2 TAG 实例构造器名称
    • 1.3 _seekHandler 搜索处理函数
    • 1.4 _needStash 需要暂存区,重写为 true
    • 1.5 _requestAbort 请求终止标志位,初始化为 false
    • 1.6 _contentLength 内容长度,初始化为 null
    • 1.7 _receivedLength 已接收长度,初始化为 0
  2. 方法

    • 2.1 destroy() 销毁实例
      • 2.1.1 如果实例处于工作中,终止加载
      • 2.1.2 执行基类的 destroy 方法
    • 2.2 open(dataSource, range) 加载数据,传入 dataSource 和 range 两个参数
      • 2.2.1 设置实例的数据源为 dataSource,实例的范围为 range
      • 2.2.2 获取实例的搜索处理函数配置(之后简称搜索配置)
      • 2.2.3 new 一个 Headers 实例(之后简称 headers)
      • 2.2.4 如果搜索配置的 headers 属性是一个对象,遍历这个属性,将它自身的键值对添加到 headers 里
      • 2.2.5 创建一个参数,等下 ajax 时用


        2.2.5
      • 2.2.6 如果 dataSource 的跨域属性为 false,设置参数的模式为 same-origin
      • 2.2.7 如果 dataSource 的证书属性为 false,设置参数的证书为 include
      • 2.2.8 设置实例的状态为连接中
      • 2.2.9 通过 fetch 实现 ajax,传入搜索配置中的 url 和参数获取响应数据
        • 2.2.9.1 如果实例的请求终止标志位为 true
          • 2.2.9.1.1 设置实例的请求终止标志位为 false
          • 2.2.9.1.2 设置实例的状态为闲置
          • 2.2.9.1.3 结束
        • 2.2.9.2 如果响应数据接收成功且状态码为 2xx
          • 2.2.9.2.1 如果响应数据头长度不为 0 且实例存在已知内容长度事件处理函数,执行之
          • 2.2.9.2.2 执行抽取函数,传入通过响应数据体的 getReader 方法获取的对象
        • 2.2.9.2 否则
          • 2.2.9.2.1 设置实例的状态为出错
          • 2.2.9.2.2 如果实例存在出错事件处理函数,执行之,传入 HTTP 状态码错误和错误数据
          • 2.2.9.2.2 否则报 HTTP 状态码无效错误
        • 2.2.9.3 若捕捉到错误
          • 2.2.9.3.1 设置实例的状态为出错
          • 2.2.9.3.2 如果实例存在出错事件处理函数,执行之,传入其他错误和错误数据
          • 2.2.9.3.2 否则抛出错误
    • 2.3 abort() 终止加载器:设置当前实例的请求终止标志位为 true
    • 2.4 _pump(reader) 抽取数据,传入 reader 参数,返回一个 Promise,为 reader 调用 read() 后的 result
      • 2.4.1 如果 result 的 done 为 true
        • 2.4.1.1 设置实例的状态为完成
        • 2.4.1.2 如果实例存在完成事件处理函数,执行之,传入实例范围起点和实例范围起点 + 实例接受数据长度 - 1
      • 2.4.1 否则
        • 2.4.1.1 如果实例的请求终止标志位为 true
          • 2.4.1.1.1 设置实例的请求终止标志位为 false
          • 2.4.1.1.2 设置实例的状态为完成
          • 2.4.1.1.3 返回 reader.cancel()
        • 2.4.1.2 设置实例的状态为缓冲中
        • 2.4.1.3 给实例的接收长度加上 result.value.buffer 的字节长度
        • 2.4.1.4 如果实例存在完成数据到达处理函数,执行之,传入 result.value.buffer、实例范围起点 + 实例接收长度、实例接收长度
        • 2.4.1.5 返回执行抽取函数的结果,传入 reader
      • 2.4.2 若捕捉到错误
        • 2.4.2.1 设置实例的状态为出错
        • 2.4.2.2 创建错误类型,初始化为 0
        • 2.4.2.3 创建错误信息,初始化为 null
        • 2.4.2.4 如果错误码为 19 且 (实例内容长度为空或实例接收长度小于实例内容长度),错误类型为加载器过早结束,错误信息同理
        • 2.4.2.4 否则错误类型为加载器其他错误,错误信息同理
        • 2.4.2.5 如果实例存在出错事件处理函数,执行之,传入错误类型和错误信息
        • 2.4.2.5 否则抛出运行时错误,传入错误信息

websocket-loader.js-WebSocketLoader类-WebSocket实时流加载器

WebSocketLoader
  1. 属性

    • 1.1 isSupported() 是否支持 WebSocket:全局有 WebSocket 属性
    • 1.2 TAG 实例构造器名称
    • 1.3 _needStash 需要暂存区,重写为 true
    • 1.4 _requestAbort 请求终止标志位,初始化为 false
    • 1.5 _ws WebSocket 实例,初始化为 null
    • 1.6 _receivedLength 已接收长度,初始化为 0
  2. 方法

    • 2.1 destroy() 销毁实例
      • 2.1.1 如果实例有 WS 实例,终止数据加载
      • 2.1.2 执行基类的 destroy 方法
    • 2.2 open(dataSource)
      • 2.2.1 new 一个 WS 实例,传入 dataSource 的 url,保存在实例的 WS 实例属性中
      • 2.2.2 设置实例 WS 实例的 binaryType 属性为 arraybuffer
      • 2.2.3 对实例 WS 实例的四个事件进行监听


        2.2.3
      • 2.2.4 设置实例的状态为连接中
      • 2.2.5 若捕获到异常
        • 2.2.5.1 设置实例的状态为出错
        • 2.2.5.2 如果实例存在出错事件处理函数,执行之,传入错误信息
        • 2.2.5.2 否则抛出运行时错误
    • 2.3 abort()
      • 2.3.1 如果实例的 WS 实例的 readyState 为 0 或者 1,就将实例的请求终止标志位设为 true,然后关闭 WS 实例
      • 2.3.2 将实例的 WS 实例设为 null
      • 2.3.3 设置实例的状态为完成
    • 2.4 _onWebSocketOpen(e) WS 打开事件处理函数
      • 2.4.1 设置实例的状态为缓冲中
    • 2.5 _onWebSocketClose(e) WS 关闭事件处理函数
      • 2.5.1 如果实例的请求终止标志位为 true,就将实例的请求终止标志位设为 false,然后结束
      • 2.5.2 设置实例的状态为完成
      • 2.5.3 如果实例存在完成事件处理函数,执行之,传入 0 和 实例接收长度 - 1
    • 2.6 _onWebSocketMessage(e) WS 接收消息事件处理函数
      • 2.6.1 将实例的接收长度加上 event.data 的字节长度
      • 2.6.2 如果实例存在数据到达事件处理函数,执行之,传入 event.data、实例接收长度
    • 2.7 _onWebSocketError(e) WS 出错事件处理函数
      • 2.7.1 设置实例的状态为出错
      • 2.7.2 如果实例存在出错事件处理函数,执行之,传入错误信息
      • 2.7.2 否则抛出运行时错误

xhr-moz-chunked-loader.js-MozChunkedLoader类-火狐加载器

MozChunkedLoader
  1. 属性

    • 1.1 isSupported() 是否支持火狐,核心是判断 XMLHttpRequest 对象的响应类型是不是 moz-chunked-arraybuffer
    • 1.2 TAG 实例构造器名称
    • 1.3 _seekHandler 搜索处理函数
    • 1.4 _needStash 需要暂存区,重写为 true
    • 1.5 _xhr XMLHttpRequest 实例
    • 1.6 _requestAbort 请求终止标志位,初始化为 false
    • 1.7 _contentLength 内容长度,初始化为 null
    • 1.8 _receivedLength 已接收长度,初始化为 0
  2. 方法

    • 2.1 destroy() 销毁实例
      • 2.1.1 如果实例处于工作中,终止加载
      • 2.1.2 释放实例的 XMLHttpRequest 实例
      • 2.1.3 执行基类的 destroy 方法
    • 2.2 open(dataSource, range) 打开数据源开始加载
      • 2.2.1 设置实例的数据源为 dataSource,实例的范围为 range
      • 2.2.2 获取实例的搜索处理函数配置(之后简称搜索配置)
      • 2.2.3 new 一个 XMLHttpRequest 实例(之后简称 xhr),保存到实例的 XMLHttpRequest 实例
      • 2.2.4 通过 xhr 的 open 方法进行 ajax,传入搜索配置的 url
      • 2.2.5 设置 xhr 的 responseType 为 moz-chunked-arraybuffer
      • 2.2.6 对 xhr 的四个事件进行监听 // 图
      • 2.2.7 如果 dataSource 的证书属性为 true,设置 xhr 的 withCredentials 属性为 true
      • 2.2.8 如果搜索配置头是一个对象,遍历搜索配置头,通过 setRequestHeader 方法将它自身的键值对设置到 xhr 的请求头中
      • 2.2.9 设置实例的状态为连接中
      • 2.2.10 启动 xhr 连接
    • 2.3 abort() 终止加载
      • 2.3.1 设置当前实例的请求终止标志位为 true
      • 2.3.2 如果实例上存在 XMLHttpRequest 实例,终止之
      • 2.3.3 设置实例的状态为完成
    • 2.4 _onReadyStateChange(e) readyState 属性改变事件处理函数
      • 2.4.1 从 e 从获取 xhr
      • 2.4.2 如果 xhr 的 readyState 等于 2
        • 2.4.2.1 如果 xhr 的 status 是 2xx
          • 2.4.2.1.1 设置实例的状态为出错
          • 2.4.2.1.2 如果实例存在出错事件处理函数,执行之,传入 HTTP 状态码错误和错误信息
          • 2.4.2.1.2 否则报 Moz HTTP 状态码无效错误
        • 2.4.2.1 否则设置实例的状态为缓冲中
    • 2.5 _onProgress(e) 进度事件处理函数
      • 2.5.1 如果实例的内容长度不为空且 e.total 不为空不为零
        • 2.5.1.1 设置实例的内容长度为 e.total
        • 2.5.1.2 如果实例存在已知内容长度事件处理函数,执行之,传入实例的内容长度
      • 2.5.2 将实例的接收长度加上 e.target.response 的字节长度
      • 2.5.3 如果实例存在完成数据到达处理函数,执行之,传入 e.target.response、实例范围起点 + 实例接收长度、实例接收长度
    • 2.6 _onLoadEnd(e) 加载停止事件处理函数
      • 2.6.1 如果实例的请求终止标志位为 true
        • 2.6.1.1 设置实例的请求终止标志位为 false,结束
      • 2.6.1 如果实例状态为出错,结束
      • 2.6.2 设置实例状态为完成
      • 2.6.3 如果实例存在完成事件处理函数,执行之,传入实例范围和实例范围 + 实例接收范围 - 1
    • 2.7 _onXhrError(e) 出错事件处理函数
      • 2.7.1 设置实例状态为出错
      • 2.7.2 如果 e.loaded 小于实例内容长度,错误类型为过早结束,错误信息填入过早结束文案
      • 2.7.2 否则,错误类型为其他错误
      • 2.7.3 如果实例存在出错事件处理函数,执行之,传入错误类型和错误信息
      • 2.7.3 否则抛出运行时错误

xhr-msstream-loader.js-MSStreamLoader类-IE加载器

MSStreamLoader

这里就不像之前介绍那么详细了,毕竟只是为了兼容 IE,所以实现起来采用不同的方式。

  1. 属性

    • 1.1 isSupported() IE 对 XMLHttpRequest 的支持,可以查询 MSStream 相关资料了解
    • 1.2 TAG
    • 1.3 _seekHandler
    • 1.4 _needStash
    • 1.5 _xhr
    • 1.6 _reader MSStreamReader
    • 1.7 _totalRange IE 专用总范围
    • 1.8 _currentRange IE 专用现范围
    • 1.9 _contentLength
    • 1.10 _receivedLength
    • 1.11 _bufferLimit IE 专用缓存限制,不然会溢出
    • 1.12 _lastTimeBufferSize 上一个属性的辅助属性
    • 1.13 _isReconnecting IE 专用重连标志位
  2. 方法

    • 2.1 destroy() 和火狐加载器的销毁实例实现大同小异
    • 2.2 open(dataSource, range) 借助了 _internalOpen 方法,isSubrange 传的是 false
    • 2.3 abort() 借助了 _internalAbort 方法,然后置实例状态为完成
    • 2.4 _internalOpen(dataSource, range, isSubrange) 区别于火狐加载器就在于第三个参数:可以加载子范围的数据,其它类似火狐加载器的 open 方法
    • 2.5 _internalAbort() 和火狐加载器的 abort 实现大同小异
    • 2.6 _xhrOnReadyStateChange(e) 和火狐加载器的 readyState 属性改变事件处理函数实现大同小异
    • 2.7 _xhrOnError(e) 和火狐加载器的出错事件处理函数实现大同小异
    • 2.8 _msrOnProgress(e) 区别于火狐加载器就在于会出现缓存太多溢出,然后重连
    • 2.9 _doReconnectIfNeeded() 重连函数:先终止再打开,起点延伸已接收长度那么多
    • 2.10 _msrOnLoad(e) 和火狐加载器的加载停止事件处理函数实现大同小异
    • 2.11 _msrOnError(e) IE 错误事件处理器,和 _xhrOnError(e) 实现差不多

xhr-range-loader.js-RangeLoader类-通用的范围加载器

重头戏来了,当前面的加载器都不支持的时候,就使用这种通用加载器。


RangeLoader
  1. 属性

常规的如我们就不重点介绍了,主要是相较之前的加载器未出现过的或非常不一样的属性详细介绍一下:
TAG、_seekHandler、_xhr、_requestAbort 不说

  • 1.1 isSupported() 核心是判断 XMLHttpRequest 对象的响应类型是不是 arraybuffer

  • 1.2 _needStash,这里重写为 false

  • 1.3 _chunkSizeKBList 常规数据块表,范围从2的7次方到13次方

  • 1.4 _currentChunkSizeKB 当前数据块大小,初始化为 384(2的9次方)

  • 1.5 _currentSpeed 当前加载速率

    • 1.5.1 关联实例只读属性 currentSpeed
  • 1.6 _currentSpeedNormalized 当前标准化的加载速率

  • 1.7 _zeroSpeedChunkCount 待加载数据块数量

  • 1.8 _speedSampler 速率计算器

  • 1.9 _waitForTotalLength 是否待加载

  • 1.10 _totalLengthReceived 是否全部接收

  • 1.11 _currentRequestRange 当前请求范围

  • 1.12 _totalLength 总长度

  • 1.13 _contentLength 内容长度

  • 1.14 _receivedLength 接收长度

  • 1.15 _lastTimeLoaded 当前接收请求子范围的长度

  1. 方法
    方法也是一样,详略分明
    destroy()、abort()、_internalOpen(dataSource, range)、_internalAbort()、_onXhrError(e) 不说
  • 2.1 open(dataSource, range) 加载数据
    • 2.1.1 设置实例的数据源为 dataSource
    • 2.1.2 设置实例的加载范围为 range
    • 2.1.3 设置实例的状态为连接中
    • 2.1.4 如果实例的已接收总长度为 0
      • 2.1.4.1 设置是否待加载为 true
      • 2.1.4.2 调用 _internalOpen,传入实例数据源,0 到 -1 的范围
    • 2.1.4 否则,调用 _openSubRange
  • 2.2 _openSubRange() 加载指定范围数据
    • 2.2.1 创建 chunkSize 为实例当前数据块大小 * 1024
    • 2.2.2 创建 from 为实例范围起点 + 实例接收长度
    • 2.2.3 创建 to 为 from + chunkSize
    • 2.2.4 如果实例的内容长度不为空且 to - 实例范围起点 >= 实例内容长度,校正 to 为实例范围起点 + 实例内容长度 - 1
    • 2.2.5 设置实例当前请求范围为 {from,to}
    • 2.2.6 调用 _internalOpen,传入实例数据源,实例当前请求范围
  • 2.3 _onReadyStateChange(e)
    • 2.3.1 从 e.target 中获取 xhr
    • 2.3.2 如果 xhr.readyState 是 2
      • 2.3.2.1 如果 xhr.status 在 [200,300) 中
        • 2.3.2.1.1 如果实例的是否待加载为 true,结束
        • 2.3.2.1.2 设置实例状态为缓冲中
      • 2.3.2.1 否则
        • 2.3.2.1.1 设置实例状态为出错
        • 2.3.2.1.2 如果实例存在出错事件处理函数,执行之,传入 HTTP 状态码错误和错误信息
        • 2.3.2.1.2 否则报 HTTP 状态码无效错误
  • 2.4 _onProgress(e) progress 事件的处理函数
    • 2.4.1 如果实例的内容长度不为空
      • 2.4.1.1 初始化一个 openNextRange 为 false
      • 2.4.1.2 如果实例是否待加载为 true
        • 2.4.1.2.1 设置实例是否待加载为 false
        • 2.4.1.2.2 设置实例是否全部接收为 true
        • 2.4.1.2.3 设置 openNextRange 为 true
        • 2.4.1.2.4 调用 _internalAbort()
        • 2.4.1.2.5 如果 e.total 不为空,设置实例总长度为它
      • 2.4.1.3 计算当前请求范围的内容长度(设置实例内容长度为实例总长度或 实例范围终点+1 - 实例范围起点)
      • 2.4.1.4 如果 openNextRange 为 true,调用 _openSubRange,结束
      • 2.4.1.5 如果实例存在已知内容长度事件处理函数,执行之,传入实例的内容长度
    • 2.4.2 创建 delta 为 e.loaded 实例当前接收请求子范围
    • 2.4.3 设置实例当前接收请求子范围的长度为 e.loaded
    • 2.4.4 将实例速率计算器增加 delta 这么多字节
  • 2.5 _normalizeSpeed(input),和 IOCtr 的标准化速率方法实现几乎一样
  • 2.6 _onLoad(e) load 事件的处理函数
    • 2.6.1 如果实例是否待加载为 true,设置实例是否待加载为 false,结束
    • 2.6.2 设置实例当前接收请求子范围的长度为 0
    • 2.6.3 获取实例速率计算器中的最近一次的速率,保存在 KBps 里
    • 2.6.4 如果 KBps 为 0
      • 2.6.4.1 将实例待加载数据块数量加 1
      • 2.6.4.2 如果实例待加载数据块数量 >= 3,让 KBps 为实例速率计算器中的平均速率
    • 2.6.5 如果 KBps 不为 0
      • 2.6.5.1 将实例当前速率设置为 KBps
      • 2.6.5.2 如果实例当前标准速率不等于标准化后的 KBps,校正实例标准速率为标准化后的 KBps,实例当前数据块大小为标准化后的 KBps
    • 2.6.6 从 e .target .response 获取 chunk
    • 2.6.7 将实例的接收长度加上 chunk 的字节长度
    • 2.6.8 创建 reportComplete,初始化为 false
    • 2.6.9 如果实例的内容长度不为空且实例接收长度 < 实例的内容长度,调用 _openSubRange 继续加载剩下的数据
    • 2.6.9 否则,设置 reportComplete 为 true
    • 2.6.10 如果实例存在完成数据到达处理函数,执行之,传入 chunk、实例范围起点 + 实例接收长度、实例接收长度
    • 2.6.11 如果 reportComplete 为 true
      • 2.6.11.1 设置实例状态为完成
      • 2.6.11.2 如果实例存在完成事件处理函数,执行之,传入实例范围起点和实例范围起点 + 实例接受数据长度 - 1

logger.js-Log类-调试工具

Log

Log 类有 2 + 5 个属性,分别是控制全局标签名和是否打开全局标签的、console 五法的控制位(error、info、warning、debug、log)

还有五个静态方法,就是 console 五法的封装:

  1. 如果没打开相应的控制位,结束
  2. 如果 tag 不存在或者启用了全局标签,tag 就设为实例全局标签名
  3. 创建 str,初始化为 [${tag}] > ${msg}
  4. 调用相应的 console 方法打印出来
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • NLP就是一次引导人们自我觉察,做出明智选择,发挥自身潜能,活出真实的自己,并且享受丰盛人生的唤醒旅程。而所谓从教...
    b18a04b7cafd阅读 3,886评论 0 0
  • 文,有风的日子 走过她 伦敦的大笨钟 是她 悬挂在时尚的街头 是你 执着的钟摆 她才走好生命的时刻 还是你 老去的...
    宁古塔诗人网阅读 729评论 0 4
  • 读书喝茶,只闻花香,不谈喜悲! 有人说,最理想的生活是:在大城市奋斗,在小城市生活。走过红尘岁月,看尽人世繁华,回...
    郝金花阅读 311评论 0 0