如何使用iOS實(shí)現(xiàn)《羋月傳》的直播、點(diǎn)播、緩存?-HTTP Live Streaming (HLS)(一)

本文博客地址:http://yangchao0033.github.io/blog/2016/01/29/hls-1/

蘋果官方對于視頻直播服務(wù)提出了 HLS 解決方案,該方案主要適用范圍在于:

  • 使用 iPhone 、iPod touch、 iPad 以及 Apple TV 進(jìn)行流媒體直播功能。(MAC 也能用)
  • 不使用特殊的服務(wù)軟件進(jìn)行流媒體直播。
  • 需要通過加密和鑒定(authentication)的視頻點(diǎn)播服務(wù)。

首先,需要大家先對 HLS 的概念進(jìn)行預(yù)覽。

HLS 的目的在于,讓用戶可以在蘋果設(shè)備(包括MAC OS X)上通過普通的網(wǎng)絡(luò)服務(wù)完成流媒體的播放。 HLS 同時支持流媒體的實(shí)時廣播點(diǎn)播服務(wù)。同時也支持不同 bit 速率的多個備用流(平時根據(jù)當(dāng)前網(wǎng)速去自適應(yīng)視頻的清晰度),這樣客戶端也好根據(jù)當(dāng)前網(wǎng)絡(luò)的帶寬去只能調(diào)整當(dāng)前使用的視頻流。安全方面,HLS 提供了通過 HTTPS 加密對媒體文件進(jìn)行加密 并 對用戶進(jìn)行驗(yàn)證,允許視頻發(fā)布者去保護(hù)自己的網(wǎng)絡(luò)。

HLS 是蘋果公司QuickTime X和iPhone軟件系統(tǒng)的一部分。它的工作原理是把整個流分成一個個小的基于HTTP的文件來下載,每次只下載一些。當(dāng)媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應(yīng)不同的數(shù)據(jù)速率。在開始一個流媒體會話時,客戶端會下載一個包含元數(shù)據(jù)的extended M3U (m3u8) playlist文件,用于尋找可用的媒體流。

HLS只請求基本的HTTP報文,與實(shí)時傳輸協(xié)議(RTP)不同,HLS可以穿過任何允許HTTP數(shù)據(jù)通過的防火墻或者代理服務(wù)器。它也很容易使用內(nèi)容分發(fā)網(wǎng)絡(luò)來傳輸媒體流。

蘋果對于自家的 HLS 推廣也是采取了強(qiáng)硬措施,當(dāng)你的直播內(nèi)容持續(xù)十分鐘
或者每五分鐘內(nèi)超過 5 MB 大小時,你的 APP 直播服務(wù)必須采用 HLS 架構(gòu),否則不允許上架。(詳情

相關(guān)服務(wù)支持環(huán)境 (重要組成)

  • Adobe Flash Media Server:從4.5開始支持HLS、Protected HLS(PHLS)。5.0改名為Adobe Media Server
  • Flussonic Media Server:2009年1月21日,版本3.0開始支持VOD、HLS、時移等。
  • RealNetworks的 Helix Universal Server :2010年4月,版本15.0開始支持iPhone, iPad和iPod的HTTP直播、點(diǎn)播H.264/AAC內(nèi)容,最新更新在2012年11月。
  • 微軟的IIS Media Services:從4.0開始支持HLS。
  • Nginx RTMP Module:支持直播模式的HLS。
  • Nimber Streamer
  • Unified Streaming Platform
  • VLC Media Player:從2.0開始支持直播和點(diǎn)播HLS。
  • Wowza Media Server:2009年12月9日發(fā)布2.0,開始全面支持HLS。
  • VODOBOX Live Server:始支持HLS。
  • Gstreamill是一個支持hls輸出的,基于gstreamer的實(shí)時編碼器。

相關(guān)客戶端支持環(huán)境

  • iOS從3.0開始成為標(biāo)準(zhǔn)功能。
  • Adobe Flash Player從11.0開始支持HLS。
  • Google的Android自Honeycomb(3.0)開始支持HLS。
  • VODOBOX HLS Player (Android,iOS, Adobe Flash Player)
  • JW Player (Adobe Flash player)
  • Windows 10 的 EDGE 瀏覽器開始支持HLS。
HLS架構(gòu)
HLS架構(gòu)

其中輸入視頻源是由攝像機(jī)預(yù)先錄制好的。之后這些源會被編碼 MPEG-4(H.264 video 和 AAC audio)格式然后用硬件打包到 MPEG-2 的傳輸流中。MPEG-2 傳輸流會被分散為小片段然后保存為一個或多個系列的 .ts 格式的媒體文件。這個過程需要借助編碼工具來完成,比如 Apple stream segmenter。

純音頻會被編碼為一些音頻小片段,通常為 ADTS頭的AAC、MP3、或者 AC-3格式。

同時上面提到的那個切片器(segmenter)也會創(chuàng)建一個索引文件,通常會包含這些媒體文件的一個列表,也能包含元數(shù)據(jù)。他一般都是一個.M38U 個hi的列表。列表元素會關(guān)聯(lián)一個 URL 用于客戶端訪問。然后按序去請求這些 URL。

服務(wù)器端

服務(wù)端可以采用硬件編碼和軟件編碼兩種形式,其功能都是按照上文描述的規(guī)則對現(xiàn)有的媒體文件進(jìn)行切片并使用索引文件進(jìn)行管理。而軟件切片通常會使用 Apple 公司提供的工具或者第三方的集成工具。

媒體編碼

媒體編碼器獲取到音視頻設(shè)備的實(shí)時信號,將其編碼后壓縮用于傳輸。而編碼格式必須配置為客戶端所支持的格式,比如 H.264 視頻和HE-AAC 音頻。當(dāng)前,支持 用于視頻的 MPEG-2 傳輸流和 純音頻 MPEG 基本流。編碼器通過本地網(wǎng)絡(luò)將 MPEG-2 傳輸流分發(fā)出去,送到流切片器那里。標(biāo)準(zhǔn)傳輸流和壓縮傳輸流無法混合使用。傳輸流可以被打包成很多種不同的壓縮格式,這里有兩個表詳細(xì)列舉了支持的壓縮格式類型。

[重點(diǎn)]在編碼中圖,不要修改視頻編碼器的設(shè)置,比如視頻大小或者編碼解碼器類型。如果避免不了,那修改動作必須發(fā)生在一個片段邊界。并且需要早之后相連的片段上用 EXT-X-DISCONTINUITY 進(jìn)行標(biāo)記。

流切片器

流切片器(通常是一個軟件)會通過本地網(wǎng)絡(luò)從上面的媒體編碼器中讀取數(shù)據(jù),然后將著這些數(shù)據(jù)一組相等時間間隔的 媒體文件。雖然沒一個片段都是一個單獨(dú)的文件,但是他們的來源是一個連續(xù)的流,切完照樣可以無縫重構(gòu)回去。

切片器在切片同時會創(chuàng)建一個索引文件,索引文件會包含這些切片文件的引用。每當(dāng)一個切片文件生成后,索引文件都會進(jìn)行更新。索引用于追蹤切片文件的有效性和定位切片文件的位置。切片器同時也可以對你的媒體片段進(jìn)行加密并且創(chuàng)建一個密鑰文件作為整個過程的一部分。

文件切片器(相對于上面的流切片器)

如果已近有編碼后的文件(而不是編碼流),你可以使用文件切片器,通過它對編碼后的媒體文件進(jìn)行 MPEG-2 流的封裝并且將它們分割為等長度的小片段。切片器允許你使用已經(jīng)存在的音視頻庫用于 HLS 服務(wù)。它和流切片器的功能相似,但是處理的源從流替換流為了文件。

媒體片段文件

媒體片段是由切片器生成的,基于編碼后的媒體源,并且是由一系列的 .ts 格式的文件組成,其中包含了你想通過 MPEG-2 傳送流攜帶的 H.264 視頻 和 AAC
/MP3/AC-3 音頻。對于純音頻的廣播,切片器可以生產(chǎn) MPEG 基礎(chǔ)音頻流,其中包含了 ADTS頭的AAC、MP3、或者AC3等音頻。

索引文件(PlayLists)

通常由切片器附帶生成,保存為 .M3U8 格式,.m3u 一般用于 MP3 音頻的索引文件。
Note如果你的擴(kuò)展名是.m3u,并且系統(tǒng)支持.mp3文件,那客戶的軟件可能要與典型的 MP3 playList 保持一致來完成 流網(wǎng)絡(luò)音頻的播放。

下面是一個 .M3U8 的 playlist 文件樣例,其中包含了三個沒有加密的十秒鐘的媒體文件:

#EXT-X-VERSION:3
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:1
 
# Old-style integer duration; avoid for newer clients.
#EXTINF:10,
http://media.example.com/segment0.ts
 
# New-style floating-point duration; use for modern clients.
#EXTINF:10.0,
http://media.example.com/segment1.ts
#EXTINF:9.5,
http://media.example.com/segment2.ts
#EXT-X-ENDLIST

為了更精確,你可以在 version 3 或者之后的協(xié)議版本中使用 float 數(shù)來標(biāo)記媒體片段的時長,并且要明確寫明版本號,如果沒有版本號,則必須與 version 1 協(xié)議保持一致。你可以使用官方提供的切片器去生產(chǎn)各種各樣的 playlist 索引文件,詳見 媒體文件切片器

分布式部分

分布式系統(tǒng)是一個網(wǎng)絡(luò)服務(wù)或者一個網(wǎng)絡(luò)緩存系統(tǒng),用于通過 HTTP 向客戶端發(fā)送媒體文件和索引文件。不用自定義模塊發(fā)送內(nèi)容。通常僅僅需要很簡單的網(wǎng)絡(luò)配置即可使用。而且這種配置一般就是限制指定 .M38U 文件和 .ts 文件的 MIME 類型。詳見 部署 HTTP Live Streaming

客戶端部分

客戶端開始時回去抓取 索引文件(.m3u8/.m3u),其中用URL來標(biāo)記不同的流。索引文件可以指定可用媒體文件的位置,解密的密鑰,以及任何可以切換的流。對于選中的流,客戶端會有序的下載每一個可獲得的文件。每一個文件都包含流中的連環(huán)碎片。一旦下載到足夠量的數(shù)據(jù),客戶端會開始向用戶展示重新裝配好的媒體資源。

客戶端負(fù)責(zé)抓取任何解密密鑰,認(rèn)證或者展示一個用于認(rèn)證的界面,之后再解密需要的文件。

這個過程會一直持續(xù)知道出現(xiàn) 結(jié)束標(biāo)記 #EXT-X-ENDLIST。如果結(jié)束標(biāo)記不出現(xiàn),該索引就是用于持續(xù)廣播的。客戶端會定期的加載一些新的索引文件。客戶端會從新更新的索引文件中去查找加密密鑰并且將關(guān)聯(lián)的URL加入到請求隊(duì)列中去。

HLS 的使用

使用 HLS 需要使用一些工具,當(dāng)然大部分工具都是服務(wù)器端使用的,這里簡單了解一下就行,包括 media stream segmenter, a media file segmenter, a stream validator, an id3 tag generator, a variant playlist generator.這些工具用英文注明是為了當(dāng)你在蘋果開發(fā)中心中尋找時方便一些。

會話模式

通常包含 Live 和 VOD (點(diǎn)播)兩種

點(diǎn)播VOD的特點(diǎn)就是可以獲取到一個靜態(tài)的索引文件,其中那個包含一套完整的資源文件地址。這種模式允許客戶端訪問全部節(jié)目。VOD點(diǎn)播擁有先進(jìn)的下載技術(shù),包括加密認(rèn)證技術(shù)和動態(tài)切換文件傳輸速率的功能(通常用于不同分辨率視頻之間的切換)。

Live 會話就是實(shí)時事件的錄制展示。它的索引文件一直處于動態(tài)變化的,你需要不斷的更新索引文件 playlist 然后移除舊的索引文件。這種類型通過向索引文件添加媒體地址可以很容易的轉(zhuǎn)化為VOD類型。在轉(zhuǎn)化時不要移除原來舊的源,而是通過添加一個 #ET-X-ENDLIST 標(biāo)記來終止實(shí)時事件。轉(zhuǎn)化時如果你的索引文件中包含 EXT-X-PLAYLIST-TYPE 標(biāo)簽,你需要將值從 EVENT 改為 VOD

ps:自己抓了一個直播的源,從索引中看到的結(jié)果是第一次回抓到代表不同帶寬的playList(抓取地址:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5.m3u8)

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,PUBLISHEDTIME=1453914627,CURRENTTIME=1454056509,BANDWIDTH=700000,RESOLUTION=1280x720
34338_PVMT5_700/index.m3u8?Dnion_vsnae=34338_PVMT5
#EXT-X-STREAM-INF:PROGRAM-ID=1,PUBLISHEDTIME=1453914627,CURRENTTIME=1454056535,BANDWIDTH=400000
34338_PVMT5_400/index.m3u8?Dnion_vsnae=34338_PVMT5
#EXT-X-STREAM-INF:PROGRAM-ID=1,PUBLISHEDTIME=1453914627,CURRENTTIME=1454056535,BANDWIDTH=1024000
34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5

這里面的鏈接不是視頻源URL,而是一個用于流切換的主索(下面會有介紹)引我猜想是需要對上一次的抓包地址做一個拼接

組合的結(jié)果就是:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5(純屬小學(xué)智力題。。。)將它作為抓取地址再一次的結(jié)果

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:134611
#EXT-X-TARGETDURATION:10
#EXTINF:9.960,
35/1454056634183_128883.ts?Dnion_vsnae=34338_PVMT5
#EXTINF:9.960,
35/1454056644149_128892.ts?Dnion_vsnae=34338_PVMT5
#EXTINF:9.960,
35/1454056654075_128901.ts?Dnion_vsnae=34338_PVMT5

同理,繼續(xù)向下抓:(拼接地址:http://dlhls.cdn.zhanqi.tv/zqlive/34338_PVMT5_1024/index.m3u8?Dnion_vsnae=34338_PVMT5/35/1454056634183_128883.ts?Dnion_vsnae=34338_PVMT5/36/1454059958599_131904.ts?Dnion_vsnae=34338_PVMT5
抓取結(jié)果:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:134984
#EXT-X-TARGETDURATION:10
#EXTINF:9.280,
36/1454059988579_131931.ts?Dnion_vsnae=34338_PVMT5
#EXTINF:9.960,
36/1454059998012_131940.ts?Dnion_vsnae=34338_PVMT5
#EXTINF:9.960,
36/1454060007871_131949.ts?Dnion_vsnae=34338_PVMT5

相比于第二次又獲取了一個片段的索引,而且只要是第二次之后,資源地址都會包含 .ts,說明里面是有視頻資源URL的,不過具體的截取方法還是需要查看前面提到的IETF的那套標(biāo)準(zhǔn)的HLS的協(xié)議,利用里面的協(xié)議應(yīng)該就能拼接出完整的資源路徑進(jìn)行下載。反正我用蘋果自帶的MPMoviePlayerController直接播放是沒有問題的,的確是直播資源。與之前說過的蘋果自帶的QuickTime類似,都遵循了HLS協(xié)議用于流媒體播放。而每次通過拼接獲取下一次的索引,符合協(xié)議里提到的不斷的更替索引的動作。

內(nèi)容加密

如果內(nèi)容需要加密,你可以在索引文件中找到密鑰的相關(guān)信息。如果索引文件中包含了一個密鑰文件的信息,那接下來的媒體文件就必須使用密鑰解密后才能解密打開了。當(dāng)前的 HLS 支持使用16-octet 類型密鑰的 AES-128 加密。這個密鑰格式是一個由著在二進(jìn)制格式中的16個八進(jìn)制組的數(shù)組打包而成的。

加密的配置模式通常包含三種:

  1. 模式一:允許你在磁盤上制定一個密鑰文件路徑,切片器會在索引文件中插入存在的密鑰文件的 URL。所有的媒體文件都使用該密鑰進(jìn)行加密。
  2. 模式二:切片器會生成一個隨機(jī)密鑰文件,將它保存在指定的路徑,并在索引文件中引用它。所有的媒體文件都會使用這個隨機(jī)密鑰進(jìn)行加密。
  3. 模式三:每 n 個片段生成一個隨機(jī)密鑰文件,并保存到指定的位置,在索引中引用它。這個模式的密鑰處于輪流加密狀態(tài)。每一組 n 個片段文件會使用不同的密鑰加密。

理論上,不定期的碎片個數(shù)生成密鑰會更安全,但是定期的生成密鑰不會對系統(tǒng)的性能產(chǎn)生太大的影響。

你可以通過 HTTP 或者 HTTPS 提供密鑰。也可以選擇使用你自己的基于會話的認(rèn)證安排去保護(hù)發(fā)送的key。更多詳情可以參考 通過 HTTPS 安全的提供預(yù)約

密鑰文件需要一個 initialization vector (IV) 去解碼加密的媒體文件。IV 可以隨著密鑰定期的改變。

緩存和發(fā)送協(xié)議

HTTPS通常用于發(fā)送密鑰,同時,他也可以用于平時的媒體片段和索引文件的傳輸。但是當(dāng)擴(kuò)展性更重要時,這樣做是不推薦的。HTTPS 請求通常都是繞開 web 服務(wù)緩存,導(dǎo)致所有內(nèi)容請求都是通過你的服務(wù)進(jìn)行轉(zhuǎn)發(fā),這有悖于分布式網(wǎng)絡(luò)連接系統(tǒng)的目的。

處于這個原因,確保你發(fā)送的網(wǎng)絡(luò)內(nèi)容都明白非常重要。當(dāng)處于實(shí)況廣播模式時索引文件不會像分片媒體文件一樣長時間的被緩存,他會動態(tài)不停地變化。

流切換

如果你的視頻具備流切換功能,這對于用戶來說是一個非常棒的體驗(yàn),處于不同的帶寬、不同的網(wǎng)速播放不同清晰度的視頻流,這樣只能的流切換可以保證用戶感覺到非常流暢的觀影體驗(yàn),同時不同的設(shè)備也可以作為選擇的條件,比如視網(wǎng)膜屏可以再網(wǎng)速良好的情況下播放清晰度更高的視頻流。

這種功能的實(shí)現(xiàn)在于,索引文件的特殊結(jié)構(gòu)

流切換索引文件結(jié)構(gòu)
流切換索引文件結(jié)構(gòu)

有別于普通的索引,具備流熱切換的索引通常由主索引和鏈接不同帶寬速率的資源的子索引,由子索引再鏈接對引得.ts視頻切片文件。其中主索引只下載一次,而子索引則會不停定期的下載,通常會先使用主索引中列出的第一個子索引,之后才會根據(jù)當(dāng)時的網(wǎng)絡(luò)情況去動態(tài)切換合適的流。客戶端會在任何時間去切換不同的流。比如連入或者退出一個 wifi 熱點(diǎn)。所有的切換都會使用相同的音頻文件(換音頻沒多大意思相對于視頻)在不同的流之間平滑的進(jìn)行切換。
這一套不同速率的視頻都是有工具生成的,使用variantplaylistcreator 工具并且為 mediafilesegmenter 或者 mediastreamsegmenter 指定 -generate-variant-playlist 選項(xiàng),詳情參考 下載工具

概念先寫到這吧,前面的知識夠?qū)SL的整體結(jié)構(gòu)做一個初步的了解。

后面一篇博客會結(jié)合代碼對整個流程進(jìn)行說明。

參考文獻(xiàn):

蘋果官方文檔

維基百科

http://my.oschina.net/CgShare/blog/302303

http://blog.csdn.net/woaifen3344/article/details/40837803

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評論 2 375

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