flv.js 1.0 源码学习(三)

整个库的核心部分之一。
参考:https://blog.csdn.net/g332065255/article/details/71158863

flv-demuxer.js-FLVDemuxer类-FLV解码器

FLVDemuxer

肯定有个什么mp4-demuxer

  1. 属性分为基本属性、媒体信息、事件处理函数,构造函数传 probeData 和 config

    • 1.1 基本属性
      • 1.1.1 TAG 构造器名称
      • 1.1.2 _config 全局配置,初始化为 conifg
      • 1.1.3 _littleEndian 判断当前是否支持小端
    • 1.2 媒体信息
      • 1.2.1 _dataOffset 数据偏移量,初始化为 probeData.dataOffset
      • 1.2.2 _firstParse 是否首次解析,初始化为 true
      • 1.2.3 _dispatch 是否已分发,初始化为 false
      • 1.2.4 _hasAudio 是否有音轨,初始化为 probeData.hasAudioTrack
      • 1.2.5 _hasVideo 是否有视轨,初始化为 probeData.hasVideoTrack
      • 1.2.6 _audioInitialMetadataDispatched 音频初始元数据是否分发,初始化为 false
      • 1.2.7 _videoInitialMetadataDispatched 视频初始元数据是否分发,初始化为 false
      • 1.2.8 _mediaInfo 媒体信息,初始化为 MediaInfo 的实例,并合并视频的是否有音轨和是否有视轨属性
      • 1.2.9 _metadata 元数据,初始化为 null
      • 1.2.10 _audioMetadata 音频元数据,初始化为 null
      • 1.2.11 _videoMetadata 视频元数据,初始化为 null
      • 1.2.12 _naluLengthSize nalu 长度,初始化为 4
      • 1.2.13 _timestampBase 基本时间戳,初始化为 0,关联了实例的 timestampBase 属性
      • 1.2.14 _timescale 时间步长,初始化为 1000
      • 1.2.15 _duration 持续时间,初始化为 0
        • 关联了实例的 overridedDuration 属性,set 的时候除了改变实例持续时间还会改变实例持续时间是否覆盖属性为 true、实例媒体信息的持续时间为它
      • 1.2.16 _durationOverrided 持续时间是否覆盖,初始化为 false
      • 1.2.17 _referenceFrameRate 参考帧率,初始化为 { fixed: true, fps: 2- 3.976, fps_num: 23976, fps_den: 1000 }
      • 1.2.18 _videoTrack 视轨,初始化为 {type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0}
      • 1.2.19 _audioTrack 音轨,初始化为 {type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0}
    • 1.3 事件处理函数,初始化都为 null,都关联了实例的对应属性
      • 1.3.1 _onError 出错事件处理函数
      • 1.3.2 _onMediaInfo 媒体信息事件处理函数
      • 1.3.3 _onTrackMetadata 轨道元数据事件处理函数
      • 1.3.4 _onDataAvailable 数据可用事件处理函数
    • 1.4 静态属性方法
      • 1.4.1 probe(buffer) 探测数据能不能解析
        • 1.4.1.1 创建 data 作为 buffer 的视图,类型为 Uint8Array
        • 1.4.1.2 如果 data 前四位不是 0x464c5601(flv头),说明不能解析,结束
        • 1.4.1.3 探测是否有音轨,依据是将 data 的第五位和 4 与运算再有符号右移 2 位不为 0
        • 1.4.1.4 探测是否有视轨,依据是将 data 的第五位和 1 与运算不为 0
        • 1.4.1.5 如果既没有音轨也没有视轨,说明不能解析,结束
        • 1.4.1.6 通过大端 32 位法读 data,index 传 5,获取 offset
        • 1.4.1.7 如果 offset 小于 9,说明不能解析,结束
        • 1.4.1.8 返回一个对象:{ match: true, consumed: offset, dataOffset: offset, hasAudioTrack: hasAudio, hasVideoTrack: hasVideo }
  2. 工具方法

    • 2.1 Swap16(src) 16位二进制数据交换,得到先右移 8 位再和 256 做与运算的结果,再得到先和 256 做与运算再左移 8 位的结果,最后一起做或运算
    • 2.2 Swap32(src) 32位二进制数据交换,得到先和 2的32次方 做与运算再右移 24 位,再得到先和 2的24次方 做与运算再右移 8 位,再得到内的数 先和 2的16次方 做与运算再左移 24 位,再得到内的数 先和 256 做与运算再左移 24 位,最后一起做或运算
    • 2.3 ReadBig32(array, index) 大端 32 位法读数据,将二进制数组的第 1 个元素向左偏移 24 位,第 2 个元素向左偏移 16 位,第 3 个元素向左偏移 8 位,第 4 个元素不偏移,最后一起做或运算
  3. 方法
    方法分为获取状态的方法、操作数据的方法、解析的方法

    • 3.1 获取状态的方法
      • 3.1.1 _isInitialMetadataDispatched() 是否是分配的初始元数据,根据实例是否有音轨和视轨来返回相应的分发元数据


        3.1.1
    • 3.2 操作数据的方法
      • 3.2.1 destroy() 销毁实例,将若干属性设为 null


        3.2.1
      • 3.2.2 bindDataSource(loader) 绑定数据源
        • 3.2.2.1 将 loader 的数据抵达事件处理函数设为实例的 parseChunks 方法
        • 3.2.2.2 返回实例
    • 3.3 解析的方法
      • 3.3.1 parseChunks(chunk, byteStart)
        • 3.3.1.1 如果实例没有四个事件处理函数,报错
        • 3.3.1.2 如果 byteStart 为 0
          • 3.3.1.2.1 如果 chunk 的字节长度大于 13
            • 3.3.1.2.1.1 探测 chunk,将探测结果保存到 probeData 中
            • 3.3.1.2.1.2 将 probeDatadataOffset 保存在 offset 中
            • 3.3.1.2.1.3 设置 byteStart 为 probeDatadataOffset
          • 3.3.1.2.2 否则,返回 0
        • 3.3.1.3 如果实例的首次解析为 true
          • 3.3.1.3.1 设置实例的首次解析为 false
          • 3.3.1.3.2 校验 byteStart 的有效性
          • 3.3.1.3.3 创建 chunk 和 offset 的 DataView
          • 3.3.1.3.4 获取这个视图的 Uint32 数据,即 prevTagSize0,期望为 0
          • 3.3.1.3.5 略过开头这 4 个字节的 tag size(prevTagSize0)
        • 3.3.1.4 只要 chunk 还没有解析完
          • 3.3.1.4.1 设置实例的分发标志位为 true
          • 3.3.1.4.2 创建 chunk 和 offset 的 DataView
          • 3.3.1.4.3 如果数据不足以解析 flv 标签,跳出循环
          • 3.3.1.4.4 从视图中获取 tagType,通过 getUint8(0)
          • 3.3.1.4.5 从视图中获取 dataSize,通过 getUint32(0, !le) & 0x00FFFFFF
          • 3.3.1.4.6 如果数据不足以解析实际的数据体,跳出循环
          • 3.3.1.4.7 如果 tagType 不是 8、9、18,直接略过 11 + dataSize + 4 个字节,重新循环
          • 3.3.1.4.8 读取视图的第 4、5、6、7 个元素,获取 ts2、ts1、ts0、ts3
          • 3.3.1.4.9 通过上一步获取时间戳
          • 3.3.1.4.10 通过视图获取流 ID,期望为 0
          • 3.3.1.4.11 创建 dataOffset 为 offset + 11
          • 3.3.1.4.12 根据 tagType 进入相应的解析子程序:8-音频、9-视频、18-脚本


            3.3.1.4.12
          • 3.3.1.4.13 获取这个视图的 Uint32 数据,即 prevTagSize0,期望为 11 + dataSize
          • 3.3.1.4.14 将 offset 加上 11 + dataSize + 4
        • 3.3.1.5 将分析后的帧分发给消费者(通常是remuxer)
          • 3.3.1.5.1 如果实例是初始化元数据分发且实例分发属性为 true 且存在视轨或音轨,调用实例的数据可用事件处理函数,传入实例视轨和实例音轨
        • 3.3.1.6 返回 offset
      • 3.3.2 _parseScriptData(arrayBuffer, dataOffset, dataSize) 解析脚本数据
        • 3.3.2.1 通过 AMF 的 parseScriptData 获取 scriptData
        • 3.3.2.2 将 scriptData 填充进实例的属性里去
          • 3.3.2.2.1 简单地将 scriptData 赋值给实例的元数据
          • 3.3.2.2.2 将实例元数据的 onMetaData 属性单独拿出来作为数据源,填充前还要判断数据类型..
          • 3.3.2.2.3 hasAudio->实例是否有音频,hasAudio->实例媒体信息是否有音频
          • 3.3.2.2.4 hasVideo->实例是否有视频,hasAudio->实例媒体信息是否有视频
          • 3.3.2.2.5 audiodatarate->实例媒体信息音率
          • 3.3.2.2.6 videodatarate->实例媒体信息视率
          • 3.3.2.2.7 width->实例媒体信息宽
          • 3.3.2.2.8 height->实例媒体信息高
          • 3.3.2.2.9 duration->实例媒体信息持续时间和实例持续时间,要处理一下时间步长
          • 3.3.2.2.10 framerate->实例引用帧率和实例媒体信息 fps
          • 3.3.2.2.11 keyframes->实例是否有帧索引和实例帧索引
          • 3.3.2.2.12 设置实例分发属性为 false
          • 3.3.2.2.13 onMetaData->实例元数据
          • 3.3.2.2.14 如果实例媒体信息是完成状态,调用媒体信息事件处理函数,传入实例媒体信息
      • 3.3.3 _parseKeyframesIndex(keyframes) - 3.3.2 的子程序,用于解析帧索引
        • 3.3.3.1 遍历 [1,keyframestimes],忽略实际上是AVC Sequence Header(AVCDecoderConfigurationRecord)的第一个关键帧,并且对 times 取了整
        • 3.3.3.2 返回一个对象,有 times 和 filepositions 两个属性
      • 3.3.4 _parseAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) 解析音频数据
        • 3.3.4.1 如果 dataSize <= 1,结束
        • 3.3.4.2 获取实例音频元数据,保存到 meta
        • 3.3.4.3 获取实例音轨,保存到 track
        • 3.3.4.4 如果需要初始化 meta
          • 3.3.4.4.1 设置 meta 的 type、id、timescale、duration


            3.3.4.4
          • 3.3.4.4.2 获取 arrayBuffer、dataOffset、dataSize 的视图
          • 3.3.4.4.3 从视图中获取 soundSpec(第一个 Uint8 字节)
          • 3.3.4.4.4 从 soundSpec 获得 soundFormat,预期是 10,也就是 AAC 格式
          • 3.3.4.4.5 从 soundSpec 获得 soundRateIndex
          • 3.3.4.4.6 根据 soundRateTable 和 soundRateIndex 获得 soundRate
          • 3.3.4.4.7 根据 soundSpec 获得 soundSize
          • 3.3.4.4.8 根据 soundSpec 获得 soundType
          • 3.3.4.4.9 设置 meta 的 audioSampleRate、channelCount、refSampleDuration、codec


            3.3.4.4.9
        • 3.3.4.5 调用 _parseAACAudioData 获取 aacData,传参数的时候偏移量加 1,大小 - 1
        • 3.3.4.6 如果 aacData 为空,结束
        • 3.3.4.7 如果 AAC 序列头存在(aacData.packetType 为 0)
          • 3.3.4.7.1 如果 meta 有 config 属性,提示冲突
          • 3.3.4.7.2 从 aacDatadata 获得 misc
          • 3.3.4.7.3 设置 meta 的 audioSampleRate、channelCount、codec、config、refSampleDuration


            3.3.4.7.3
          • 3.3.4.7.4 如果实例是初始化元数据分发且实例分发属性为 true 且有音轨或者视轨,调用实例的数据可用事件处理函数,传入实例视轨和实例音轨
          • 3.3.4.7.4 否则,设置实例的音频初始化元数据分发属性为 true
          • 3.3.4.7.5 通知新的元数据,设置实例的分发属性为 false
          • 3.3.4.7.6 调用轨道元数据处理函数,传入 'audio' 和 meta
          • 3.3.4.7.7 从实例媒体信息获取 mi,设置 mi 的 audioCodec、audioSampleRate、audioChannelCount
          • 3.3.4.7.8 如果 mi 有视轨且 mi 的视频编码不为空,设置 mi 的 mimeType 为 video/x-flv; codecs= 打头
          • 3.3.4.7.8 否则,设置 mi 的mimeType 为 video/x-flv; codecs= 打头
          • 3.3.4.7.9 如果 mi 已完成,调用实例的媒体信息事件处理函数
          • 3.3.4.7.10 结束
        • 3.3.4.7 如果是 AAC 原帧数据
          • 3.3.4.7.1 将 dts 设置为实例基本时间戳 + tagTimestamp
          • 3.3.4.7.2 获取 aacSample
          • 3.3.4.7.3 往 tracksamples 里推入 aacSample
          • 3.3.4.7.4 给 track 的长度加上 aacDatadata 的长度
        • 3.3.4.7 否则打印不支持的 AAC 数据信息
      • 3.3.5 _parseAACAudioData(arrayBuffer, dataOffset, dataSize) 解析 AAC 音频数据
        • 3.3.5.1 如果 dataSize 小于 1,说明 AAC 数据无效,结束
        • 3.3.5.2 创建一个 array,Uint8Array 类型,指向 arrayBuffer,起点为 dataOffset,大小为 dataSize
        • 3.3.5.3 如果 array[0] 为 0,通过调用 _parseAACAudioSpecificConfig 获取 data
        • 3.3.5.3 否则,data 为 arraysubarray(1)
        • 3.3.5.4 返回一个对象,键为 packetType(array[0])和 data
      • 3.3.6 _parseAACAudioSpecificConfig(arrayBuffer, dataOffset, dataSize) 获取 AAC 数据描述
        • 3.3.6.1 创建 array,实例化一个 Uint8Array,传入 arrayBuffer, dataOffset, dataSize
        • 3.3.6.2 创建 audioObjectType 和 originalAudioObjectType,初始值是 array[0] 右移三位
        • 3.3.6.3 创建 samplingIndex,期望是有效的 AAC 采样频率指数
        • 3.3.6.4 创建 samplingFrequence 为 mpeg 采样率
        • 3.3.6.5 创建 channelConfig,期望是有效的信道配置
        • 3.3.6.6 如果 audioObjectType 为 5,即 HE-AAC,设置 extensionSamplingIndex 和 audioExtensionObjectType
        • 3.3.6.7 如果是火狐浏览器
          • 3.3.6.7.1 如果频率小于24kHz,则使用 SBR(HE-AAC)
          • 3.3.6.7.2 否则,使用 LC-AAC
        • 3.3.6.7 如果是安卓浏览器,使用 LC-AAC
        • 3.3.6.7 否则,使用 HE-AAC
        • 3.3.6.8 装填 config
        • 3.3.6.9 返回一个描述对象


          3.3.6.9
      • 3.3.7 _parseVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition) 解析视频数据
        • 3.3.7.1 如果 dataSize <= 1,结束
        • 3.3.7.2 创建一个 Uint8Array 数组,指向 arrayBuffer,起点为 dataOffset,大小为 dataSize,创建 spec 为它的第一个元素
        • 3.3.7.3 创建 frameType、codecId,期望 codecId 为 7
        • 3.3.7.4 调用 _parseAVCVideoPacket,略过一位偏移量
      • 3.3.8 _parseAVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, framType) 解析 AVC 视频数据包
        • 3.3.8.1 如果 dataSize <= 4,结束
        • 3.3.8.2 创建一个视图,指向 arrayBuffer,起点为 dataOffset,大小为 dataSize
        • 3.3.8.3 从视图中获取 packetType,为第一个 Uint8 数据
        • 3.3.8.4 从视图中获取 cts
        • 3.3.8.5 如果 AVCDecoderConfigurationRecord(packetType 是 0),调用 _parseAVCDecoderConfigurationRecord,传入 arrayBuffer, dataOffset + 4, dataSize - 4
        • 3.3.8.5 如果有一个或多个 Nalus(packetType 是 1),传入 arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts
        • 3.3.8.5 如果为空的 AVC 序列结束,什么也不做
        • 3.3.8.5 否则,调用出错事件处理函数
      • 3.3.9 _parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize) 解析 AVC 解码器配置记录
        • 3.3.9.1 如果 dataSize <= 7,结束
        • 3.3.9.2 创建一个视图,指向 arrayBuffer,起点为 dataOffset,大小为 dataSize
        • 3.3.9.3 获取实例视频元数据,保存到 meta
        • 3.3.9.4 获取实例视轨,保存到 track
        • 3.3.9.5 如果需要初始化 meta
          • 3.3.9.5.1 设置 meta 的 type、id、timescale、duration


            3.3.9.5.1
        • 3.3.9.6 从视图中获取 version(第一个 Uint8 字节),期望为 1
        • 3.3.9.7 从视图中获取 avcProfile(第二个 Uint8 字节),期望为 0
        • 3.3.9.8 从视图中获取 profileCompatibility(第三个 Uint8 字节)
        • 3.3.9.9 从视图中获取 avcLevel(第四个 Uint8 字节)
        • 3.3.9.10 从视图中获取 nalu 长度放进实例 nalu 长度属性中,期望为 3 或者 4
        • 3.3.9.11 从视图中获取 spsCount,期望为 1
        • 3.3.9.12 创建 offset,初始化为 6
        • 3.3.9.13 循环 spsCount 次
          • 3.3.9.13.1 从视图中获取 len
          • 3.3.9.13.2 offset 加 2
          • 3.3.9.13.3 如果 len 为 0,继续循环
          • 3.3.9.13.4 创建 sps 为一个 Uint8Array 数组,指向 arrayBuffer,起点是 dataOffset + offset,长度是 len
          • 3.3.9.13.5 offset 加 len
          • 3.3.9.13.6 创建 config,值是 SPSParserparseSPS(sps) 得到的
          • 3.3.9.13.7 设置 meta 的基本视频描述信息


            3.3.9.13.7
          • 3.3.9.13.8 校正 metaframeRate


            3.3.9.13.8
          • 3.3.9.13.9 校正 metarefSampleDuration


            3.3.9.13.9
          • 3.3.9.13.10 拼接出来 metacodec


            3.3.9.13.10
          • 3.3.9.13.11 设置实例媒体信息的基本视频描述信息


            3.3.9.13.11
          • 3.3.9.13.12 拼接实例媒体信息的 mimeType 属性
          • 3.3.9.13.13 如果实例媒体信息已完成,调用媒体信息事件处理函数
        • 3.3.9.14 从视图获取 ppsCount(图片参数集合),预期是 1,从 offset 开始算
        • 3.3.9.15 offset 加 1
        • 3.3.9.16 循环 offset 次
          • 3.3.9.16.1 从视图中获取 len
          • 3.3.9.16.2 offset 加 2
          • 3.3.9.16.3 如果 len 为 0,继续循环
          • 3.3.9.16.4 offset 加 len
        • 3.3.9.17 设置 meta 的 avcc
        • 3.3.9.18 如果实例是初始化元数据分发且实例分发属性为 true 且存在视轨或音轨,调用实例的数据可用事件处理函数,传入实例视轨和实例音轨
        • 3.3.9.18 否则设置实例的视频初始化元数据分发属性为 true
        • 3.3.9.19 通知新的元数据,设置实例的分发属性为 false
        • 3.3.9.20 调用轨道元数据事件处理函数,传入 'video'meta
      • 3.3.10 _parseAVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) 解析 AVC 视频数据
        • 3.3.10.1 创建一个视图,指向 arrayBuffer,起点为 dataOffset,大小为 dataSize
        • 3.3.10.2 创建 units、length、offset
        • 3.3.10.3 获取 lengthSize、dts、keyframe


          3.3.10.3
        • 3.3.10.4 只要 offset < dataSize,循环
          • 3.3.10.4.1 如果 offset + 4 >= dataSize,结束
          • 3.3.10.4.2 获取 naluSize,并根据 lengthSize 校正
          • 3.3.10.4.3 获取 unitType,如果是 5,设置 keyframe 为 true
          • 3.3.10.4.4 获取 data,是一个 Uint8Array 的数组,指向 arrayBuffer,起点是 dataOffset + offset,长度是 lengthSize + naluSize
          • 3.3.10.4.5 创建 unit,初始化为 {type: unitType, data: data}
          • 3.3.10.4.6 往 units 推入 unit
          • 3.3.10.4.7 给 length 加上 data 的字节长度
          • 3.3.10.4.8 给 offset 加上 lengthSize + naluSize
        • 3.3.10.5 如果 units 不为空
          • 3.3.10.5.1 创建 track,初始化为实例视轨
          • 3.3.10.5.2 创建 avcSample,填充 avc 的采样数据


            3.3.10.5.2
          • 3.3.10.5.3 在 tracksamples 里推入 avcSample
          • 3.3.10.5.4 给 tracklength 加上 length

demux-errors.js-DemuxErrors常量-解码错误

  1. OK 成功
  2. FORMAT_ERROR 格式错误
  3. FORMAT_UNSUPPORTED 不支持的格式
  4. CODEC_UNSUPPORTED 不支持的编码

amf-parser.js-AMF类-AMF解析器

AMF

parseValue 是入口
解析十二选七种二进制数据,将他们序列化格式化:

  1. Number √
  2. Boolean √
  3. String √
  4. Object √
  5. MovieClip (reserved, not supported)
  6. Null
  7. Undefined
  8. Reference
  9. ECMA array √
  10. Object end marker √
  11. Strict array √
  12. Date √
  13. Long string √

二进制->对象描述信息

utf8-conv-decodeUTF8函数-UTF8转Unicode器

移植自c++的一个库:https://github.com/m13253/libWinTF8
可以将一个 TypeArray 二进制数组的内容转成 Unicode 码

sps-parser.js-SPSParser类-SPS解码器

SPSParser

获取视频的宽、高、帧等信息,俗称序列参数集
https://zhuanlan.zhihu.com/p/27896239

exp-golomb.js-ExpGolomb类-哥伦布指数解码器

ExpGolomb

http://www.cnblogs.com/DwyaneTalk/p/4035206.html

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

推荐阅读更多精彩内容

  • 二、IO io-controller.js-IOController类-IO控制器 属性分为基本信息、暂存区信息、...
    云峰yf阅读 6,078评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 二进制数组(ArrayBuffer对象、TypedArray视图和DataView视图)是JavaScript操作...
    呼呼哥阅读 21,221评论 2 12
  • 原文: objective-c常用方法列表(总结)在简书保存一份 第1章Objective-C学习环境准备1.1O...
    14cat阅读 1,129评论 0 2
  • 周末 去新装修待散味的房子里打扫卫生 阳光甚好 先去二里外的矮山转悠转悠 这还是当年学过跆拳道爬过泰山的人 但事实...
    秋月_白阅读 248评论 0 0