預(yù)加載視頻實(shí)現(xiàn)快速播放

原文地址:https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload

在以往的項(xiàng)目中寂恬,只要有視頻的存在,那么就會(huì)是個(gè)讓人費(fèi)神的項(xiàng)目闰挡。且不說(shuō)對(duì)它的適配兼容問(wèn)題举娩,只說(shuō)它的加載問(wèn)題就能說(shuō)上半天了析校。本文作者從視頻預(yù)加載的各種方法入手,討論了如何讓視頻播放速度更快的解決辦法铜涉。

眾所周知勺良,能更快速的播放視頻意味著會(huì)有更的多人觀看到你的視頻。在本文中骄噪,我將探索通過(guò)用戶主動(dòng)觸發(fā)預(yù)加載資源來(lái)加速視頻播放的技術(shù)尚困。

注意: 除非另有說(shuō)明,否則本文也適用于audio元素链蕊。

視頻地址:https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload

致謝:版權(quán)所有Blender Foundation | www.blender.org 事甜。

TL; DR

這很棒…但…

視頻preload屬性易用于Web服務(wù)器上托管的唯一文件。瀏覽器可能完全忽略該屬性滔韵。

HTML文檔完全加載和解析后逻谦,資源才開(kāi)始獲取。

當(dāng)應(yīng)用程使用MSE擴(kuò)展媒體時(shí)陪蜻,MSE會(huì)忽略媒體元素上的preload屬性邦马。

Link preload強(qiáng)制瀏覽器發(fā)出視頻資源請(qǐng)求,但不會(huì)阻止文檔的onload事件宴卖。HTTP Range請(qǐng)求不兼容滋将。

兼容MSE和文檔片斷。獲取完整資源時(shí)症昏,文件只能是小型媒體(< 5MB)随闽。

手動(dòng)緩沖完全控制復(fù)雜的錯(cuò)誤需要網(wǎng)頁(yè)來(lái)處理。

視頻預(yù)加載(preload)屬性

如果視頻資源是托管在Web服務(wù)器上的唯一文件肝谭,您可能會(huì)使用 video 標(biāo)簽的?preload屬性來(lái)提示瀏覽器預(yù)加載的信息或內(nèi)容量掘宪。 但這意味著Media Source Extensions(MSE)與?preload?將不兼容蛾扇。

資源的獲取將僅在HTML文檔初始加載和解析完成后啟動(dòng)(例如,?DOMContentLoaded事件已觸發(fā))魏滚,而實(shí)際上在獲取資源時(shí)將觸發(fā)完全不同的?window.onload事件镀首。

將?preload屬性設(shè)置為?metadata表示用戶不想馬上加載視頻,但是需要預(yù)先獲取其元數(shù)據(jù)(尺寸鼠次,軌道列表蘑斧,時(shí)長(zhǎng)等)。 請(qǐng)注意须眷,從Chrome 64開(kāi)始,?preload的默認(rèn)值是?metadata(以前是?auto?)沟突。

<video?id="video"?preload="metadata"?src="file.mp4"?controls></video>

????<script>

????????video.addEventListener('loadedmetadata',?function()?{

????????if?(video.buffered.length?===?0)?return;

????????????var?bufferedSeconds?=?video.buffered.end(0)?-?video.buffered.start(0);

????????????console.log(bufferedSeconds?+?'?seconds?of?video?are?ready?to?play!');

????????});

????</script>


將?preload屬性設(shè)置為?auto表示瀏覽器將緩存整個(gè)視頻花颗,無(wú)需暫停緩沖,可以支持完整播放惠拭。

<video?id="video"?preload="auto"?src="file.mp4"?controls></video>


????<script>

????????video.addEventListener('loadedmetadata',?function()?{

????????if?(video.buffered.length?===?0)?return;

????????????var?bufferedSeconds?=?video.buffered.end(0)?-?video.buffered.start(0);

????????????console.log(bufferedSeconds?+?'?seconds?of?video?are?ready?to?play!');

????????});

????</script>

由于?preload屬性只是一個(gè)提示扩劝,瀏覽器可能會(huì)完全忽略?preload屬性。寫(xiě)到這职辅,請(qǐng)注意以下Chrome中的一些應(yīng)用規(guī)則:

? 啟用Data Saver后 棒呛,Chrome 會(huì)強(qiáng)制設(shè)置?preload值為?none?。

? 在Android 4.3中域携,由于Android的bug簇秒,Chrome 會(huì)強(qiáng)制設(shè)置?preload值為?none。

? 在蜂窩連接(2G秀鞭,3G和4G)時(shí)趋观,Chrome 會(huì)強(qiáng)制設(shè)置?preload值為?metadata?。

提示

如果您的網(wǎng)站在同一個(gè)域中包含多個(gè)視頻資源锋边,我建議您將?preload值設(shè)置為?metadata或定義?poster屬性并將?preload設(shè)置為?none?皱坛。 這樣,可以避免在同一域名中HTTP連接數(shù)達(dá)到最大時(shí)導(dǎo)致資源加載掛起(根據(jù)HTTP 1.1規(guī)范6)豆巨。 請(qǐng)注意剩辟,如果視頻不屬于您的核心用戶體驗(yàn),這樣做也會(huì)提高網(wǎng)頁(yè)加載速度往扔。

Link preload

正如其他文章所述 贩猎,link preload是一種聲明性資源獲取,允許您強(qiáng)制瀏覽器在不阻止?window.onload事件和頁(yè)面加載的情況下發(fā)出資源請(qǐng)求萍膛。 通過(guò)?<linkrel="preload">預(yù)加載的資源在DOM融欧、JavaScript或CSS沒(méi)有明確引用之前,被存儲(chǔ)在本地瀏覽器中卦羡。

預(yù)加載preload與預(yù)獲取prefetch不同之處在于它側(cè)重于當(dāng)前導(dǎo)航并根據(jù)其類型的優(yōu)先級(jí)(腳本噪馏,樣式麦到,字體,視頻欠肾,音頻等)獲取資源瓶颠。它通常用于為當(dāng)前會(huì)話預(yù)熱瀏覽器緩存。

預(yù)加載完整視頻

以下示例講述了是如何在您的網(wǎng)站上預(yù)加載完整視頻刺桃,以便當(dāng)您的JavaScript請(qǐng)求獲取視頻內(nèi)容時(shí)粹淋,它會(huì)從緩存中讀取,因?yàn)橐曨l資源可能已被瀏覽器緩存瑟慈。 如果預(yù)加載請(qǐng)求尚未完成桃移,則將進(jìn)行常規(guī)網(wǎng)絡(luò)獲取。

<link rel="preload" as="video" >


<video id="video" controls></video>


<script>

// Later on, after some condition has been met, set video source to the

// preloaded video URL.

video.src = 'https://cdn.com/small-file.mp4';

video.play().then(_ => {

// If preloaded video URL was already cached, playback started immediately.

});

</script>

注意: 我建議僅將其用于小型媒體文件(<5MB)葛碧。

由于link預(yù)加載的?as屬性值為?video借杰,所以預(yù)加載資源將由例子中的視頻元素使用。如果它是一個(gè)音頻元素进泼,它將是?as="audio"蔗衡。

預(yù)加載第一個(gè)片段

下面的示例顯示了如何用?<linkrel="preload">來(lái)預(yù)加載視頻的第一段內(nèi)容,并將其與Media Source Extensions一起使用乳绕。 如果您不熟悉 MSE Javascript API绞惦,請(qǐng)參閱MSE基礎(chǔ)知識(shí)。

為簡(jiǎn)單起見(jiàn)洋措,我們假設(shè)整個(gè)視頻已被拆分為若干較小的文件,如“file1.webm”济蝉,“file2.webm”,“file_3.webm”等菠发。

<link rel="preload" as="fetch" >


<video id="video" controls></video>


<script>

const mediaSource = new MediaSource();

video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });


function sourceOpen() {

URL.revokeObjectURL(video.src);

const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');


// If video is preloaded already, fetch will return immediately a response

// from the browser cache (memory cache). Otherwise, it will perform a

// regular network fetch.

fetch('https://cdn.com/file_1.webm')

.then(response => response.arrayBuffer())

.then(data => {

// Append the data into the new sourceBuffer.

sourceBuffer.appendBuffer(data);

// TODO: Fetch file_2.webm when user starts playing video.

})

.catch(error => {

// TODO: Show "Video is not available" message to user.

});

}

</script>

警告: 對(duì)于跨域問(wèn)題堆生,請(qǐng)確保正確設(shè)置了CORS請(qǐng)求頭。 由于我們無(wú)法使用fetch(videoFileUrl, { mode: ‘no-cors’ })檢索未知響應(yīng)所創(chuàng)建的緩存數(shù)組雷酪,因此我們無(wú)法將其提供給視頻或音頻元素淑仆。

支持

由于link preload 尚未在每個(gè)瀏覽器中得到支持。您可以使用下面的代碼檢測(cè)其可用性哥力,以調(diào)整您的展現(xiàn)效果蔗怠。

function preloadFullVideoSupported() {

const link = document.createElement('link');

link.as = 'video';

return (link.as === 'video');

}


function preloadFirstSegmentSupported() {

const link = document.createElement('link');

link.as = 'fetch';

return (link.as === 'fetch');

}

手動(dòng)緩沖

在我們深入了解Cache API和Service Worker之前,讓我們看看如何使用MSE手動(dòng)緩沖視頻吩跋。 下面的例子模擬了支持HTTP Range請(qǐng)求的Web服務(wù)器寞射,但這種方法與緩存文件片段非常相似。 請(qǐng)注意锌钮,一些插件庫(kù)如Google的Shaka Player 桥温,JW Player和Video.js都可以為您處理此問(wèn)題。

<video id="video" controls></video>


<script>

const mediaSource = new MediaSource();

video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });


function sourceOpen() {

URL.revokeObjectURL(video.src);

const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');


// Fetch beginning of the video by setting the Range HTTP request header.

fetch('file.webm', { headers: { range: 'bytes=0-567139' } })

.then(response => response.arrayBuffer())

.then(data => {

sourceBuffer.appendBuffer(data);

sourceBuffer.addEventListener('updateend', updateEnd, { once: true });

});

}


function updateEnd() {

// Video is now ready to play!

var bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);

console.log(bufferedSeconds + ' seconds of video are ready to play!');


// Fetch the next segment of video when user starts playing the video.

video.addEventListener('playing', fetchNextSegment, { once: true });

}


function fetchNextSegment() {

fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })

.then(response => response.arrayBuffer())

.then(data => {

const sourceBuffer = mediaSource.sourceBuffers[0];

sourceBuffer.appendBuffer(data);

// TODO: Fetch further segment and append it.

});

}

</script>

注意事項(xiàng)

由于您現(xiàn)在已有控制緩沖整個(gè)媒體的能力梁丘,我建議您在預(yù)加載時(shí)考慮下使用設(shè)備的電池電量侵浸、用戶的“Data-Saver 模式”首選項(xiàng)和網(wǎng)絡(luò)信息等因素旺韭。

電池意識(shí)

在考慮預(yù)加載視頻之前,請(qǐng)考慮用戶設(shè)備的電池電量掏觉。 這將在電量較低時(shí)保持電池壽命区端。

當(dāng)設(shè)備電池電量快耗盡時(shí),禁用預(yù)加載或預(yù)加載分辨率較低的視頻澳腹。

if ('getBattery' in navigator) {

navigator.getBattery()

.then(battery => {

// If battery is charging or battery level is high enough

if (battery.charging || battery.level > 0.15) {

// TODO: Preload the first segment of a video.

}

});

}

檢測(cè)“Data-Saver”

使用?Save-Data客戶端提示請(qǐng)求頭為在瀏覽器中啟動(dòng)“流量節(jié)省”模式的用戶提供快速輕便的應(yīng)用程序织盼。通過(guò)識(shí)別此請(qǐng)求頭,您的應(yīng)用程序可以通過(guò)自定義限制成本和限制性能的方法為用戶提供更好的用戶體驗(yàn)酱塔。

通過(guò)閱讀?使用Save-Data提供快速和輕量級(jí)應(yīng)用程序?全文沥邻,了解更多信息 。

基于網(wǎng)絡(luò)信息的智能加載

您可以在預(yù)加載之前檢查?navigator.connection.type?羊娃。當(dāng)它設(shè)置為?cellular?時(shí)唐全,您可以阻止預(yù)加載并提示用戶他們的移動(dòng)網(wǎng)絡(luò)運(yùn)營(yíng)商可能正在為帶寬收費(fèi),并且只自動(dòng)回放以前緩存的內(nèi)容迁沫。

if ('connection' in navigator) {

if (navigator.connection.type == 'cellular') {

// TODO: Prompt user before preloading video

} else {

// TODO: Preload the first segment of a video.

}

}

查看?網(wǎng)絡(luò)信息示例?了解如何對(duì)網(wǎng)絡(luò)更改做出反應(yīng)。

預(yù)緩存多個(gè)第一片段

如果我們想在不知道用戶最終將選擇哪一個(gè)視頻進(jìn)行播放的情況下捌蚊,預(yù)先加載一些視頻集畅,那該如何操作呢。假設(shè)用戶在瀏覽一個(gè)具有10個(gè)視頻的網(wǎng)頁(yè)缅糟,我們有足夠的內(nèi)存來(lái)緩存每個(gè)視頻文件挺智,但我們肯定不會(huì)去創(chuàng)建10個(gè)隱藏的video標(biāo)簽和10個(gè)?MediaSource對(duì)象以及它們的數(shù)據(jù)。

下面的兩個(gè)部分示例向您展示了如何使用功能強(qiáng)大且易用的Cache API來(lái)預(yù)緩存多個(gè)第一視頻片段窗宦。需要注意的是赦颇,使用IndexedDB也可以實(shí)現(xiàn)類似的功能。這里我們沒(méi)有使用Service Workers赴涵,是因?yàn)镃ache API也可以從Window對(duì)象中訪問(wèn)媒怯。

Fetch和Cache

const videoFileUrls = [

'bat_video_file_1.webm',

'cow_video_file_1.webm',

'dog_video_file_1.webm',

'fox_video_file_1.webm',

];


// Let's create a video pre-cache and store all first segments of videos inside.

window.caches.open('video-pre-cache')

.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));


function fetchAndCache(videoFileUrl, cache) {

// Check first if video is in the cache.

return cache.match(videoFileUrl)

.then(cacheResponse => {

// Let's return cached response if video is already in the cache.

if (cacheResponse) {

return cacheResponse;

}

// Otherwise, fetch the video from the network.

return fetch(videoFileUrl)

.then(networkResponse => {

// Add the response to the cache and return network response in parallel.

cache.put(videoFileUrl, networkResponse.clone());

return networkResponse;

});

});

}

請(qǐng)注意,如果我要使用 HTTP Range 請(qǐng)求髓窜,則必須手動(dòng)重新創(chuàng)建?Response對(duì)象扇苞,因?yàn)镃ache API尚不支持Range響應(yīng)。 還要注意的是寄纵,在調(diào)用?networkResponse.arrayBuffer()時(shí)會(huì)立即響應(yīng)鳖敷,并將獲取到的全部?jī)?nèi)容渲染器內(nèi)存中,這也是您可能希望使用小范圍的原因程拭。

作為參考定踱,我修改了上面例子的部分代碼,將HTTP Range請(qǐng)求的視頻保存到預(yù)緩存中恃鞋。

return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })

.then(networkResponse => networkResponse.arrayBuffer())

.then(data => {

const response = new Response(data);

// Add the response to the cache and return network response in parallel.

cache.put(videoFileUrl, response.clone());

return response;

});


播放視頻

當(dāng)用戶點(diǎn)擊播放按鈕時(shí)崖媚,我們將獲取Cache API中可用的第一段視頻亦歉,以便在它可用時(shí)能立即開(kāi)始播放。否則至扰,我們需要從網(wǎng)絡(luò)中獲取它鳍徽。 需要注意的是,瀏覽器和用戶可能會(huì)清除緩存 敢课。

如前所述阶祭,我們使用MSE將視頻的第一片段傳給video元素。

function onPlayButtonClick(videoFileUrl) {

video.load(); // Used to be able to play video later.


window.caches.open('video-pre-cache')

.then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.

.then(response => response.arrayBuffer())

.then(data => {

const mediaSource = new MediaSource();

video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });


function sourceOpen() {

URL.revokeObjectURL(video.src);


const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

sourceBuffer.appendBuffer(data);


video.play().then(_ => {

// TODO: Fetch the rest of the video when user starts playing video.

});

}

});

}

警告: 對(duì)于跨域問(wèn)題直秆,請(qǐng)確保正確設(shè)置了CORS請(qǐng)求頭濒募。 由于我們無(wú)法使用fetch(videoFileUrl, { mode: ‘no-cors’ })檢索未知響應(yīng)所創(chuàng)建的緩存數(shù)組技俐,因此我們無(wú)法將其提供給視頻或音頻元素载迄。

使用Service Worker創(chuàng)建Range響應(yīng)

現(xiàn)在崖咨,如果您已獲取整個(gè)視頻文件并將其保存在Cache API中古掏。 當(dāng)瀏覽器發(fā)送 HTTP Range請(qǐng)求時(shí)拒炎,您肯定不希望將整個(gè)視頻存入渲染器內(nèi)存硅堆,因?yàn)?Cache API尚不支持 Range 響應(yīng)沪曙。

那么痘括,讓我演示下如何攔截這些請(qǐng)求并從service worker返回自定義的Range響應(yīng)歇竟。

function onPlayButtonClick(videoFileUrl) {

video.load(); // Used to be able to play video later.


window.caches.open('video-pre-cache')

.then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.

.then(response => response.arrayBuffer())

.then(data => {

const mediaSource = new MediaSource();

video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });


function sourceOpen() {

URL.revokeObjectURL(video.src);


const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

sourceBuffer.appendBuffer(data);


video.play().then(_ => {

// TODO: Fetch the rest of the video when user starts playing video.

});

}

});

}

重點(diǎn)是要注意我使用?response.blob()重新創(chuàng)建了這個(gè)切片響應(yīng)挥唠,因?yàn)檫@只是讓我可以( 在Chrome中 )處理文件,而?response.arrayBuffer()會(huì)將整個(gè)文件存入渲染器內(nèi)存焕议。

我的自定義?X-From-CacheHTTP 響應(yīng)頭可用于判斷此請(qǐng)求是來(lái)自緩存還是來(lái)自網(wǎng)絡(luò)宝磨。也可以用于像ShakaPlayer等播放器用它來(lái)忽略作為網(wǎng)絡(luò)速度指示的響應(yīng)時(shí)間。

視頻地址:https://developers.google.com/web/fundamentals/media/fast-playback-with-video-preload

這里有一個(gè)官方媒體應(yīng)用程序的視頻例子 盅安,特別是它的ranged-response.js文件唤锉,講解了如何處理Range請(qǐng)求的完整解決方案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末别瞭,一起剝皮案震驚了整個(gè)濱河市窿祥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝙寨,老刑警劉巖壁肋,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異籽慢,居然都是意外死亡浸遗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)箱亿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)跛锌,“玉大人,你說(shuō)我怎么就攤上這事∷杳保” “怎么了菠赚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)郑藏。 經(jīng)常有香客問(wèn)我衡查,道長(zhǎng),這世上最難降的妖魔是什么必盖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任拌牲,我火速辦了婚禮,結(jié)果婚禮上歌粥,老公的妹妹穿的比我還像新娘塌忽。我一直安慰自己,他們只是感情好失驶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布土居。 她就那樣靜靜地躺著,像睡著了一般嬉探。 火紅的嫁衣襯著肌膚如雪擦耀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天涩堤,我揣著相機(jī)與錄音眷蜓,去河邊找鬼。 笑死定躏,一個(gè)胖子當(dāng)著我的面吹牛账磺,可吹牛的內(nèi)容都是我干的芹敌。 我是一名探鬼主播痊远,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼氏捞!你這毒婦竟也來(lái)了碧聪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤液茎,失蹤者是張志新(化名)和其女友劉穎逞姿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捆等,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滞造,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了栋烤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒养。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖明郭,靈堂內(nèi)的尸體忽然破棺而出买窟,到底是詐尸還是另有隱情丰泊,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布始绍,位于F島的核電站瞳购,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亏推。R本人自食惡果不足惜学赛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望径簿。 院中可真熱鬧罢屈,春花似錦、人聲如沸篇亭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)译蒂。三九已至曼月,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柔昼,已是汗流浹背哑芹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捕透,地道東北人聪姿。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乙嘀,于是被迫代替她去往敵國(guó)和親末购。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350