<video>解析及分片上傳

一、<video>標(biāo)簽解析

 <video
    ref="video"
    x5-video-player-type="h5"
    x5-video-player-fullscreen="true"
    x5-playsinline
    webkit-playsinline
    playsinline
    preload="auto"
    controlslist="nodownload noremoteplayback"
    disablePictureInPicture
    :src="src"
    :poster="videoImage"
    @play="onVideoPlay"
    @pause="onVideoPause"
    @ended="onEnded"
    @timeupdate="onVideoTimeUpdate"
    @error="onVideoError"
    @waiting="onWaiting"
    @click="changePlay"
    @loadedmetadata="loadedmetadata"
    @loadeddata="loadeddata"
    @canplay="onVideoCanPlay"
    @canplaythrough="canplaythrough"
    @seeking="onSeeking"
    @seeked="onseeked"
 />

常用屬性解析

x5-video-player-type:(只支持安卓)啟用同層H5播放器妥泉,就是在視頻全屏的時(shí)候吸奴,div可以呈現(xiàn)在視頻層上,也是WeChat安卓版特有的屬性愧怜。同層播放別名也叫做沉浸式播放呀页,播放的時(shí)候看似全屏,但是已經(jīng)除去了control和微信的導(dǎo)航欄拥坛,只留下"X"和"<"兩鍵蓬蝶。目前的同層播放器只在Android(包括微信)上生效,暫時(shí)不支持iOS猜惋。至于為什么同層播放只對(duì)安卓開(kāi)放丸氛,是因?yàn)榘沧坎荒芟馡OS一樣局域播放,默認(rèn)的全屏?xí)沟靡恍┙缑娌僮鞅蛔钄r著摔,如果是全屏H5還好缓窜,但是做直播的話,諸如彈幕那樣的功能就無(wú)法實(shí)現(xiàn)了谍咆,所以這時(shí)候同層播放的概念就解決了這個(gè)問(wèn)題禾锤。

x5-video-player-fullscreen:(只支持安卓)全屏設(shè)置。它又兩個(gè)屬性值摹察,ture和false恩掷,true支持全屏播放,false不支持全屏播放供嚎。IOS 微信瀏覽器是Chrome的內(nèi)核螃成,不支持X5同層播放。安卓微信瀏覽器是X5內(nèi)核查坪,一些屬性標(biāo)簽比如playsinline就不支持寸宏,所以始終全屏。

x5-playsinline:(只支持安卓)使視頻內(nèi)聯(lián)播放

webkit-playsinline:可以使視頻內(nèi)聯(lián)播放(解決IOS端偿曙,Android不支持)

playsinline:IOS微信瀏覽器支持小窗內(nèi)播放氮凝,布爾屬性,指明視頻將內(nèi)聯(lián)(inline)播放望忆,即在元素的播放區(qū)域內(nèi)罩阵。請(qǐng)注意竿秆,沒(méi)有此屬性并不意味著視頻始終是全屏播放的。

preload就:該枚舉屬性旨在提示瀏覽器稿壁,作者認(rèn)為在播放視頻之前幽钢,加載哪些內(nèi)容會(huì)達(dá)到最佳的用戶體驗(yàn)「凳牵可能是下列值之一:

  1. none: 表示不應(yīng)該預(yù)加載視頻匪燕。

  2. metadata: 表示僅預(yù)先獲取視頻的元數(shù)據(jù)(例如長(zhǎng)度)。

  3. auto: 表示可以下載整個(gè)視頻文件喧笔,即使用戶不希望使用它帽驯。

  4. 空字符串: 和值為 auto 一致。每個(gè)瀏覽器的默認(rèn)值都不相同书闸,即使規(guī)范建議設(shè)置為 metadata尼变。

controls:加上這個(gè)屬性,瀏覽器會(huì)在視頻底部提供一個(gè)控制面板浆劲,允許用戶控制視頻的播放嫌术,包括音量,跨幀牌借,暫停/恢復(fù)播放度气。

controlslist :當(dāng)瀏覽器顯示視頻底部的播放控制面板(例如,指定了 controls 屬性)時(shí)走哺,controlslist 屬性會(huì)幫助瀏覽器選擇在控制面板上顯示哪些控件。允許接受的值有 nodownload, nofullscreen 和 noremoteplayback哲虾。如果要禁用畫(huà)中畫(huà)模式(和控件)丙躏,請(qǐng)使用 disablePictureInPicture 屬性。

disablePictureInPicture:防止瀏覽器顯示畫(huà)中畫(huà)上下文菜單或在某些情況下自動(dòng)請(qǐng)求畫(huà)中畫(huà)模式束凑。該屬性可以禁用 video 元素的畫(huà)中畫(huà)特性晒旅,右鍵菜單中的“畫(huà)中畫(huà)”選項(xiàng)會(huì)被禁用。

src:要嵌到頁(yè)面的視頻的 URL汪诉》狭担可選;你也可以使用 video 塊內(nèi)的 <source> 元素來(lái)指定需要嵌到頁(yè)面的視頻扒寄。

poster:海報(bào)幀圖片 URL鱼鼓,用于在視頻處于下載中的狀態(tài)時(shí)顯示。如果未指定該屬性该编,則在視頻第一幀可用之前不會(huì)顯示任何內(nèi)容迄本,然后將視頻的第一幀會(huì)作為海報(bào)(poster)幀來(lái)顯示。

loop:布爾屬性课竣;指定后嘉赎,會(huì)在視頻播放結(jié)束的時(shí)候置媳,自動(dòng)返回視頻開(kāi)始的地方,繼續(xù)播放公条。

muted:布爾屬性拇囊,指明在視頻中音頻的默認(rèn)設(shè)置。設(shè)置后靶橱,音頻會(huì)初始化為靜音寥袭。默認(rèn)值是 false, 意味著視頻播放的時(shí)候音頻也會(huì)播放。

常用事件解析

play:播放已開(kāi)始抓韩。

pause:播放已暫停纠永。

ended:視頻停止播放,因?yàn)?media 已經(jīng)到達(dá)結(jié)束點(diǎn)谒拴。(視頻已播完)

timeupdate:currentTime 屬性指定的時(shí)間發(fā)生變化尝江。(視頻播放時(shí)間變化,可以獲取event.target.currentTime, event.target.duration)

error:視頻錯(cuò)誤處理

waiting:由于暫時(shí)缺少數(shù)據(jù)英上,播放已停止炭序。(視頻流加載中或者視頻播放暫停)

click:點(diǎn)擊視頻

loadedmetadata:已加載元數(shù)據(jù)。

loadeddata:media 中的首幀已經(jīng)完成加載苍日。

canplay:瀏覽器可以播放媒體文件了惭聂,但估計(jì)沒(méi)有足夠的數(shù)據(jù)來(lái)支撐播放到結(jié)束,不必停下來(lái)進(jìn)一步緩沖內(nèi)容相恃。(視頻可以開(kāi)始辜纲,但不確定是否會(huì)播放)

canplaythrough:瀏覽器估計(jì)它可以在不停止內(nèi)容緩沖的情況下播放媒體直到結(jié)束误堡。(視頻一定可以可播放)

seeking:跳幀(seek)操作開(kāi)始傲诵。

seeked:跳幀(seek)操作完成。

視頻內(nèi)聯(lián)播放處理

為兼容ios和安卓蓬衡,需要加上這幾個(gè)屬性:

x5-video-player-type="h5"
x5-video-player-fullscreen="true"
x5-playsinline
webkit-playsinline
playsinline

自動(dòng)播放

android始終不能自動(dòng)播放杀糯。ios10后版本的safari和微信都不讓視頻自動(dòng)播放了(順帶音頻也不能自動(dòng)播放了)扫俺,但微信提供了一個(gè)事件WeixinJSBridgeReady,在微信嵌入webview全局的這個(gè)事件觸發(fā)后固翰,視頻仍可以自動(dòng)播放狼纬,這個(gè)應(yīng)該是現(xiàn)在在ios端微信的視頻自動(dòng)播放的比較靠譜的方式,其他如手q或者其他瀏覽器骂际,建議就引導(dǎo)用戶觸發(fā)觸屏的行為后操作比較好疗琉。

// 引用微信jssdk
<script-- src="http://xxxx/jweixin-1.6.0.js"></script>
// 頁(yè)面初始化后執(zhí)行自動(dòng)播放
wx && wx.getNetworkType({
    complete: () => {
      const videoEle = this.$refs.video
      videoEle.play()
    }
})
// 或者這樣
WeixinJSBridge.invoke('getNetworkType', {}, (e)=> {
    const videoEle = this.$refs.video
    videoEle.play()
});

二、調(diào)起系統(tǒng)攝像獲取視頻及視頻時(shí)長(zhǎng)

本地上傳支持格式

視頻在ios上本地上傳后能播放的文件類型:mov歉铝,mp4没炒,m4v(微信會(huì)壓縮視頻,而且限制大小,mov或者mp4后綴改m4v可防止視頻在微信中被壓縮)送火,3gp(手機(jī)上能播放拳话,但是手機(jī)上input標(biāo)簽選不了3gp格式的視頻)
視頻在安卓上本地上傳后能播放的文件類型:mov,mp4种吸,m4v弃衍,3gp(手機(jī)上能播放,但是手機(jī)上input標(biāo)簽選不了3gp格式的視頻)坚俗,webm
總結(jié):視頻在ios镜盯,安卓能在本地上傳后都能播放的文件類型:mov,mp4猖败,m4v

手機(jī)錄像支持格式

視頻在錄像中支持上傳的文件類型(手機(jī)自帶攝像基本覆蓋ios和安卓了):mov速缆,mp4

三、h5調(diào)起系統(tǒng)攝像功能&獲取視頻時(shí)長(zhǎng)

// html
<input ref="input" accept="video/*" type="file" capture="user" @change="onChange" />
<!-- 虛擬組件恩闻,不做展示艺糜,用來(lái)獲取視頻時(shí)長(zhǎng) -->
<video
  ref="video"
  :src="videoUrl"
  class="hidden-video"
  :muted="false"
  controls="controls"
  @loadedmetadata="loadedmetadata"
></video>

// js
/** 錄制 */
onChange(event) {
  const { target } = event
  const file = target.files[0]
  this.videoFile = file
  if (!file) return
  this.isLoadedmetadata = false
  this.isSubmit = false
  // 釋放內(nèi)存
  if (this.videoUrl) {
    URL.revokeObjectURL(this.videoUrl)
  }
  // 這句后會(huì)執(zhí)行l(wèi)oadedmetadata方法往下走
  this.videoUrl = URL.createObjectURL(file)
  console.log('videoUrl: ', this.videoUrl)
  // 需要先加載微信jssdk
  // 自動(dòng)播放hack:兼容ios小程序webview中用戶手動(dòng)點(diǎn)擊播放才會(huì)觸發(fā)加載事件
  try {
    if (window.wx) {
      wx.getNetworkType({
        complete: () => {
          const videoEle = this.$refs.video
          videoEle.play()
          videoEle.pause()
        }
      })
    }
  } catch (e) {
    console.log(e)
  }
  setTimeout(() => {
    // 兜底:如果不兼容loadedmetadata則直接提交視頻
    if (!this.isLoadedmetadata) {
      this.isSubmit = true
      this._videoUpload()
    }
  }, 2000)
},
/** 已加載好元數(shù)據(jù):微信及瀏覽器支持 */
loadedmetadata(event) {
  console.log('loadedmetadata 視頻時(shí)長(zhǎng)為', event.target.duration)
  if (this.isSubmit) return
  this.isLoadedmetadata = true
  this.videoDuration = event.target.duration || 0
  this._videoUpload()
},
_videoUpload() {}

調(diào)起視頻錄制:當(dāng)accept="video/*"時(shí)capture只有兩種值,一種是user幢尚,用于調(diào)用面向人臉的攝像頭(例如手機(jī)前置攝像頭),一種是environment破停,用于調(diào)用環(huán)境攝像頭(例如手機(jī)后置攝像頭)
獲取視頻時(shí)長(zhǎng):通過(guò)input的file拿到video鏈接URL.createObjectURL(file),觸發(fā)loadedmetadata事件拿到視頻時(shí)長(zhǎng)尉剩,需要注意的是ios的小程序webview中需要觸發(fā)自動(dòng)播放后才能觸發(fā)加載事件真慢。
資源預(yù)覽:通過(guò)input=flie得到文件file對(duì)象,如果需要預(yù)覽理茎,不管是視頻還是圖片黑界,可以通過(guò)URL.createObjectURL()來(lái)實(shí)現(xiàn)格式轉(zhuǎn)換。通過(guò)此方法會(huì)得到本地的blob URL皂林,然后將此URL賦值給img的src朗鸠,或者video的src,即可預(yù)覽式撼。

export const getFileSrc = (file) => {
    return URL.createObjectURL(file)
}

用戶錄像后video加載事件兼容

環(huán)境 安卓 ios
瀏覽器 loadedmetadata童社,loadeddata求厕,canplaythrough支持 只loadedmetadata支持著隆,自動(dòng)播放后三個(gè)都支持
微信 loadedmetadata,loadeddata呀癣,canplaythrough支持 只loadedmetadata支持美浦,自動(dòng)播放后三個(gè)都支持
小程序內(nèi)嵌h5 loadedmetadata,loadeddata项栏,canplaythrough 支持 都不支持浦辨,自動(dòng)播放后三個(gè)都支持

四、視頻及其他文件通用校驗(yàn)

通過(guò)對(duì)文件類型及大小進(jìn)行校驗(yàn)判斷沼沈。

// 文件類型
export const FILE_TYPE = {
  VIDEO: 'video',
  IMAGE: 'image',
  AUDIO: 'audio'
}

// 錯(cuò)誤類型
export const ERROR_TYPE = {
  ERROR_FILE_TYPE: 'ERROR_FILE_TYPE',
  ERROR_FILE_SIZE: 'ERROR_FILE_SIZE'
}

// 文案
const TYPE_MAP_TEXT = {
  [FILE_TYPE.VIDEO]: '視頻',
  [FILE_TYPE.IMAGE]: '圖片',
  [FILE_TYPE.AUDIO]: '音頻'
}

// 限制類型
const ACCEPT_TYPE = {
  [FILE_TYPE.VIDEO]: ['mp4', 'mov', 'm4v'],
  [FILE_TYPE.IMAGE]: ['png', 'jpeg', 'jpg'],
  [FILE_TYPE.AUDIO]: ['mp3', 'wav', 'mov']
}

// 限制大小: 單位M
const MAX_SIZE = {
  [FILE_TYPE.VIDEO]: 50,
  [FILE_TYPE.IMAGE]: 10,
  [FILE_TYPE.AUDIO]: 30
}

/**
 * 上傳文件校驗(yàn)
 * @param {File} file 文件<arrayBuffer>
 * @param {Number} type 需要判斷的文件類型:視頻video流酬,圖片image币厕,音頻audio
 * @param {Array} acceptType 限制的文件類型
 * @param {Array} maxSize 限制的文件大小,單位 M
 * @param {String} tipMsg 錯(cuò)誤提示
 * @returns { errorMsg: 錯(cuò)誤信息, errorType: 錯(cuò)誤類型 }
 */
export default function ({
  file,
  type = FILE_TYPE.VIDEO,
  acceptType,
  maxSize,
  tipMsg = '上傳',
  errorFileTypeMsg = '',
  errorFileSizeMsg = ''
} = {}) {
  let errorMsg = ''
  let errorType = ''
  if (!file) return
  const fileMimeType = file.type
  const fileName = file.name
  const fileType = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()
  const fileSize = file.size / 1024 / 1024 // M
  const fileAcceptType = acceptType || ACCEPT_TYPE[type]
  const fileMaxSize = maxSize || MAX_SIZE[type]
  const pageId = log.getPageId()
  console.log('file:', file)
  console.log(`${pageId}_upload_${type}_info`, `${fileType}_${fileSize}`)
  const isValidMimeType = fileMimeType.includes(type)
  // 判斷類型
  if (!isValidMimeType || (isValidMimeType && !fileAcceptType.includes(fileType))) {
    console.log(`${pageId}_upload_type_error`, fileType)
    errorMsg =
      errorFileTypeMsg || `不支持當(dāng)前${TYPE_MAP_TEXT[type]}格式芽腾,僅支持${fileAcceptType.join('旦装、')} ,請(qǐng)重新${tipMsg}`
    errorType = ERROR_TYPE.ERROR_FILE_TYPE
  }
  // 判斷大小
  else if (fileSize > fileMaxSize) {
    console.log(`${pageId}_upload_size_over`, fileSize)
    errorMsg = errorFileSizeMsg || `${TYPE_MAP_TEXT[type]}大小不能超過(guò)${fileMaxSize}M摊滔,請(qǐng)重新${tipMsg}`
    errorType = ERROR_TYPE.ERROR_FILE_SIZE
  }
  return { errorMsg, errorType }
}

調(diào)用通用校驗(yàn):

const validInfo = isValidFile({
    file: this.videoFile,
    type: FILE_TYPE.VIDEO,
    acceptType: ['mp4', 'mov'],
    maxSize: 30,
    tipMsg: '錄制',
    errorFileSizeMsg: `視頻過(guò)大阴绢,請(qǐng)?jiān)?-5秒內(nèi)錄制`
  })
  if (validInfo.errorMsg) {
    return this.$toast.show({ content: validInfo.errorMsg })
  }

五、視頻分片上傳

視頻分片思路:
1艰躺、將文件按照指定分片分成n等分呻袭,最后一份為剩余份數(shù)
2、按照視頻分片順序依次上傳文件分片給后端
3腺兴、按照當(dāng)前視頻次序計(jì)算上傳進(jìn)度
4左电、上傳成功/失敗后回調(diào)
5、后端拿到分片視頻按照視頻順序進(jìn)行拼接含长,進(jìn)行存儲(chǔ)或者返回給前端

import { request } from '@/plugins/request'
import { UPLOAD_CHUNK_FILE } from '@const/api/modules/common'

const SHARD_UPLOAD_SIZE = 5 // 分片限制5M

/**
 * 分片上傳
 * @param {File} file 文件<arrayBuffer>
 * @param {Number} maxChunkSize 分片最大size券腔,默認(rèn)5M
 * @param {Object} params 額外的業(yè)務(wù)參數(shù)
 * @param {Function} onProgress 上傳進(jìn)度回調(diào)
 * @param {Function} onSuccess 分片上傳完畢回調(diào)
 * @param {Function} onError 錯(cuò)誤回調(diào)
 * @returns 文件md5
 */
export class ChunkUpload {
  constructor({
    api = UPLOAD_CHUNK_FILE,
    file,
    maxChunkSize = SHARD_UPLOAD_SIZE,
    params = {},
    onProgress = () => {},
    onSuccess = () => {},
    onError = () => {}
  } = {}) {
    this.api = api
    this.file = file // 源文件
    this.fileSize = file.size // 文件大小
    this.fileName = file.name // 文件名
    this.shardSize = maxChunkSize * 1024 * 1024 // 分片大小
    this.shardCount = Math.ceil(this.fileSize / this.shardSize) // 分片數(shù)
    this.index = 0 // 當(dāng)前分片序號(hào) 從0開(kāi)始
    this.params = params
    this.fileInfoId = ''
    this.isUploading = false
    this.progress = 0 // 整個(gè)文件上傳進(jìn)度
    this.onProgress = onProgress
    this.onSuccess = onSuccess
    this.onError = onError
  }
  async upload() {
    // id:時(shí)間_文件大小
    this.fileInfoId = `${new Date().getTime()}_${this.fileSize}`
    this.uploadHandle()
  }
  async uploadHandle() {
    this.isUploading = true
    const start = this.index * this.shardSize
    const end = start + this.shardSize
    const packet = this.file.slice(start, end) // 將文件進(jìn)行切片
    let chunkShardSize = this.shardSize
    if (this.shardCount === this.index + 1) {
      // 最后一片大小
      chunkShardSize = this.fileSize - this.index * this.shardSize
    }
    request({
      api: this.api,
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      params: {
        id: this.fileInfoId,
        chunkCount: this.shardCount, // 分片總長(zhǎng)度
        fileName: this.fileName,
        length: this.fileSize,
        chunkIndex: this.index + 1, // 當(dāng)前是第幾片
        chunkLength: chunkShardSize, // 當(dāng)前片大小
        file: packet // slice方法用于切出文件的一部分
      },
      onUploadProgress: (progressEvent) => {
        let currentProcessStatus = (progressEvent.loaded / progressEvent.total) * 100 || 0
        // 當(dāng)前分片占整個(gè)文件的占比
        const chunkShardRatio = chunkShardSize / this.fileSize
        if (this.index + 1 < this.shardCount) {
          this.progress = chunkShardRatio * this.index * 100 + chunkShardRatio * currentProcessStatus
        } else {
          // 最后一片
          this.progress = (this.shardSize / this.fileSize) * this.index * 100 + chunkShardRatio * currentProcessStatus
        }
        this.progress = Math.floor(this.progress)
        this.onProgress(this.progress)
      }
    })
      .then((res) => {
        if (this.index + 1 >= this.shardCount) {
          const { fileName, fileId, fileHash, picFileId, picFileHash } = res.data.result || {}
          this.isUploading = false
          this.onProgress(100)
          this.onSuccess({
            fileName,
            fileId,
            fileHash,
            picFileId,
            picFileHash
          })
        } else {
          this.index < this.shardCount && this.index++
          this.uploadHandle()
        }
      })
      .catch((err) => {
        console.error('fail', err)
        this.onError(err)
      })
  }
}

調(diào)用分片上傳:

// 分片上傳
uploadFpsChunk() {
    const chunkUploadInstance = new ChunkUpload({
      api: UPLOAD_FPS_CHUNK,
      file: this.videoFile,
      onProgress: (progress) => {
        console.log('上傳進(jìn)度:', progress)
      },
      onSuccess: (fileInfo = {}) => {
        console.log('分片上傳完畢:', fileInfo)
        console.log(fileInfo)
      },
      onError: (err) => {
        console.error('分片上傳失敗:', err)
        console.error({ msg: err.msg || '上傳中斷拘泞,請(qǐng)檢查網(wǎng)絡(luò)' })
      }
    })
    chunkUploadInstance.upload()
},

參考文檔

  1. video標(biāo)簽特殊屬性詳解:https://blog.csdn.net/web_ding/article/details/112601894

  2. <video>: 視頻嵌入元素:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纷纫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子陪腌,更是在濱河造成了極大的恐慌辱魁,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诗鸭,死亡現(xiàn)場(chǎng)離奇詭異染簇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)强岸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)锻弓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝌箍,你說(shuō)我怎么就攤上這事青灼。” “怎么了妓盲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵杂拨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我悯衬,道長(zhǎng)弹沽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮策橘,結(jié)果婚禮上炸渡,老公的妹妹穿的比我還像新娘。我一直安慰自己丽已,他們只是感情好偶摔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著促脉,像睡著了一般辰斋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘸味,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天宫仗,我揣著相機(jī)與錄音,去河邊找鬼旁仿。 笑死藕夫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枯冈。 我是一名探鬼主播毅贮,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尘奏!你這毒婦竟也來(lái)了滩褥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炫加,失蹤者是張志新(化名)和其女友劉穎瑰煎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俗孝,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酒甸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赋铝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片插勤。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖革骨,靈堂內(nèi)的尸體忽然破棺而出农尖,到底是詐尸還是另有隱情,我是刑警寧澤苛蒲,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布卤橄,位于F島的核電站绿满,受9級(jí)特大地震影響臂外,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一漏健、第九天 我趴在偏房一處隱蔽的房頂上張望嚎货。 院中可真熱鬧,春花似錦蔫浆、人聲如沸殖属。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洗显。三九已至,卻和暖如春原环,著一層夾襖步出監(jiān)牢的瞬間挠唆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工嘱吗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玄组,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓谒麦,卻偏偏與公主長(zhǎng)得像俄讹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绕德,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容