小視頻錄制和播放

前段時(shí)間項(xiàng)目開發(fā)過(guò)程中遇到一個(gè)需求计盒,想做一個(gè)類似微信那樣的小視頻媚朦,然后在錄制視頻的自定義圖層上播放。于是就研究了 AVFoundation 的一些東西植康。實(shí)際開發(fā)過(guò)程中也遇到了一些問(wèn)題彤灶,所以在這里做下記錄看幼。另外參考了SBVideoCaptureDemo的源碼。

使用AVCaptureSession幌陕、AVCaptureMovieFileOutput诵姜、AVCaptureDeviceInput、AVCaptureVideoPreviewLayer來(lái)錄制視頻搏熄,并通過(guò)AVAssetExportSeeion壓縮視頻并轉(zhuǎn)換為 MP4 格式棚唆。

使用AVPlayerLayer暇赤、AVPlayer、AVPlayerItem瑟俭、NSURL自定義播放視頻

1翎卓、視頻的錄制


判斷用戶的設(shè)備對(duì)視頻錄制的支持情況

1、視頻錄制之前要先判斷攝像頭是否可用摆寄。

2失暴、攝像頭是否被授權(quán)。

自定義頻錄制

對(duì)所用的幾個(gè)類做簡(jiǎn)單說(shuō)明

AVCaptureSession:媒體(音微饥、視頻)捕獲會(huì)話逗扒,負(fù)責(zé)把捕獲的音視頻數(shù)據(jù)輸出到輸出設(shè)備中。一個(gè)AVCaptureSession可以有多個(gè)輸入輸出流欠橘。

AVCaptureDevice:輸入設(shè)備矩肩,包括麥克風(fēng)、攝像頭肃续,通過(guò)該對(duì)象可以設(shè)置物理設(shè)備的一些屬性(例如相機(jī)聚焦等)黍檩。

AVCaptureDeviceInput:設(shè)備輸入數(shù)據(jù)管理對(duì)象,可以根據(jù)AVCaptureDevice創(chuàng)建對(duì)應(yīng)的AVCaptureDeviceInput對(duì)象始锚,該對(duì)象將會(huì)被添加到AVCaptureSession中管理刽酱。

AVCaptureVideoPreviewLayer:相機(jī)拍攝預(yù)覽圖層,是CALayer的子類瞧捌,使用該對(duì)象可以看到視頻錄制效果棵里,創(chuàng)建該對(duì)象需要指定對(duì)應(yīng)的AVCaptureSession對(duì)象。

AVCaptureMovieFileOutput:視頻輸出流姐呐。把一個(gè)輸入或者輸出添加到AVCaptureSession之后AVCaptureSession就會(huì)在所有相符的輸入殿怜、輸出設(shè)備之間 建立連接(AVCaptionConnection)。

//此狀態(tài)表示視頻制作時(shí)的各個(gè)狀態(tài)

typedefNS_ENUM(NSInteger, VideoState)

{

VideoStateFree = 0,

VideoStateWillStartRecord,

VideoStateDidStartRecord,

VideoStateWillEndRecord,

VideoStateDidEndRecord,

VideoStateWillStartMerge,

VideoStateDidStartMerge,

};

//與VideoState不同

//此狀態(tài)表示用戶操作時(shí)的狀態(tài)曙砂,比如:已經(jīng)開始錄制头谜、停止錄制

typedefNS_ENUM(NSInteger, RecordOptState)

{

RecordOptStateFree = 0,

RecordOptStateBegin,

RecordOptStateEnd,

};

//錄制時(shí)用戶手指所處區(qū)域,可以用來(lái)判斷是在錄制區(qū)域還是在取消錄制區(qū)域

typedefNS_ENUM(NSInteger, CurrentRecordRegion)

{

CurrentRecordRegionFree = 0,

CurrentRecordRegionRecord,

CurrentRecordRegionCancelRecord,

};

初始化相關(guān)設(shè)置

self.captureSession= [[AVCaptureSessionalloc]init];

AVCaptureDevice*frontCamera =nil;

AVCaptureDevice*backCamera =nil;

NSArray*cameras = [AVCaptureDevicedevicesWithMediaType:AVMediaTypeVideo];

for(AVCaptureDevice*cameraincameras) {

if(AVCaptureDevicePositionFront== camera.position) {//前置攝像頭

frontCamera = camera;

}

elseif(AVCaptureDevicePositionBack== camera.position)

{

backCamera = camera;

}

//默認(rèn)使用后攝像機(jī)

[backCamera lockForConfiguration:nil];//先鎖定設(shè)備

if([backCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {

[backCamerasetExposureMode:AVCaptureExposureModeContinuousAutoExposure];//曝光量調(diào)節(jié)

}

if([backCameraisFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {//焦點(diǎn)CGPoint

[backCamerasetFocusMode:AVCaptureFocusModeContinuousAutoFocus];

}

[backCameraunlockForConfiguration];

[self.captureSessionbeginConfiguration];

//input device

self.videoDeviceInput= [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:nil];

AVCaptureDeviceInput*audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]error:nil];

if([self.captureSessioncanAddInput:self.videoDeviceInput]) {

[self.captureSessionaddInput:self.videoDeviceInput];

}

if([self.captureSessioncanAddInput:audioDeviceInput]) {

[self.captureSessionaddInput:audioDeviceInput];

}

//output device

self.movieFileOutput= [[AVCaptureMovieFileOutputalloc]init];

if([self.captureSessioncanAddOutput:self.movieFileOutput]) {

[self.captureSessionaddOutput:self.movieFileOutput];

}

//preset

if([self.captureSessioncanSetSessionPreset:AVCaptureSessionPreset640x480]) {

self.captureSession.sessionPreset=AVCaptureSessionPreset640x480;//AVCaptureSessionPresetLow

}

//preview layer

self.preViewLayer= [AVCaptureVideoPreviewLayerlayerWithSession:self.captureSession];

self.preViewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;

[self.captureSession commitConfiguration];

[self.captureSession startRunning];//會(huì)話 開始運(yùn)行

注意:改變?cè)O(shè)備屬性前一定要首先調(diào)用lockForConfiguration方法加鎖,調(diào)用完之后使用unlockForConfiguration方法解鎖鸠澈。對(duì)相機(jī)設(shè)置時(shí)乔夯,要判斷當(dāng)前設(shè)備是否支持改設(shè)置。比如:isExposureModeSupported款侵、isFocusModeSupported等。

//開始錄制

- (void)startRecordingToOutputFileURL

{

_videoState=VideoStateWillStartRecord;

_recordOptState=RecordOptStateBegin;

//根據(jù)設(shè)備輸出獲得連接

AVCaptureConnection*captureConnection = [self.movieFileOutputconnectionWithMediaType:AVMediaTypeVideo];

//根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)

if(![self.movieFileOutputisRecording]) {

//預(yù)覽圖層和視頻方向保持一致

captureConnection.videoOrientation= [self.preViewLayerconnection].videoOrientation;

[self.movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:[self getVideoSaveFilePathString]] recordingDelegate:self];//開始錄制

}

else

{

[selfstopCurrentVideoRecording];

}

//停止錄制

- (void)stopCurrentVideoRecording

{

[self stopCountDurTimer];//停止計(jì)時(shí)器

_videoState=VideoStateWillEndRecord;

[self.movieFileOutput stopRecording];//停止錄制

}

#pragma mark - AVCaptureFileOutputRecordingDelegate

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections

{

_videoState = VideoStateDidStartRecord;

self.videoSaveFilePath = [fileURL absoluteString];

self.currentFileURL = fileURL;

self.currentVideoDur = 0.0f;

self.totalVideoDur = 0.0f;

[self startCountDurTimer];//啟動(dòng)錄制計(jì)時(shí)器

//這里拋出開始錄制 代理

if (RecordOptStateEnd == _recordOptState) {//時(shí)間太短侧纯,還沒開始錄制新锈,就已經(jīng)松開了錄制按鈕,要停止正在錄制的視頻

[self stopCurrentVideoRecording];

}

}

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error

{

_videoState = VideoStateDidEndRecord;

self.totalVideoDur += _currentVideoDur;

//這里拋出錄制完成 代理

if (CurrentRecordRegionRecord == [self getCurrentRecordRegion]) {

if (self.totalVideoDur < MIN_VIDEO_DUR) {//錄制時(shí)間太短

[self removeMovFile];//移除mov格式的視頻文件

_videoState = VideoStateFree;

}

}

else

{

[self removeMovFile];//移除mov格式的視頻文件

_videoState = VideoStateFree;

}

}

//將mov格式轉(zhuǎn)化成MP4

- (void)mergeAndExportVideosAtFileURLs:(NSArray*)fileURLArray

{

_videoState = VideoStateWillStartMerge;

NSError *error = nil;

//渲染尺寸

CGSize renderSize = CGSizeMake(0, 0);

NSMutableArray *layerInstructionArray = [NSMutableArray array];

//用來(lái)合成視頻

AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];

CMTime totalDuration = kCMTimeZero;

//先取assetTrack 也為了取renderSize

NSMutableArray *assetTrackArray = [NSMutableArray array];

NSMutableArray *assetArray = [NSMutableArray array];

for (NSURL *fileURL in fileURLArray) {

//AVAsset:素材庫(kù)里的素材

AVAsset *asset = [AVAsset assetWithURL:fileURL];

if (!asset) {

continue;

}

[assetArray addObject:asset];

//素材的軌道

AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];//返回一個(gè)數(shù)組AVAssetTracks資產(chǎn)

[assetTrackArray addObject:assetTrack];

renderSize.width = MAX(renderSize.width, assetTrack.naturalSize.height);

renderSize.height = MAX(renderSize.height, assetTrack.naturalSize.width);

}

CGFloat renderW = 320;//MIN(renderSize.width, renderSize.height);

for (NSInteger i = 0; i < [assetArray count] && i < assetTrackArray.count; i++) {

AVAsset *asset = [assetArray objectAtIndex:i];

AVAssetTrack *assetTrack = [assetTrackArray objectAtIndex:i];

//文件中的音頻軌道,里面可以插入各種對(duì)應(yīng)的素材

AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

NSArray*dataSourceArray= [asset tracksWithMediaType:AVMediaTypeAudio];//獲取聲道眶熬,即麥克風(fēng)相關(guān)信息

[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:((dataSourceArray.count > 0)?[dataSourceArray objectAtIndex:0]:nil) atTime:totalDuration error:nil];

//工程文件中的軌道妹笆,有音頻軌块请,里面可以插入各種對(duì)應(yīng)的素材

AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:assetTrack atTime:totalDuration error:&error];

//視頻軌道中的一個(gè)視頻,可以縮放拳缠、旋轉(zhuǎn)等

AVMutableVideoCompositionLayerInstruction *layerInstrucition = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

totalDuration = CMTimeAdd(totalDuration, asset.duration);

CGFloat rate = renderW / MIN(assetTrack.naturalSize.width, assetTrack.naturalSize.height);

CGAffineTransform layerTransform = CGAffineTransformMake(assetTrack.preferredTransform.a, assetTrack.preferredTransform.b, assetTrack.preferredTransform.c, assetTrack.preferredTransform.d, assetTrack.preferredTransform.tx * rate, assetTrack.preferredTransform.ty * rate);

layerTransform = CGAffineTransformConcat(layerTransform, CGAffineTransformMake(1, 0, 0, 1, 0, -(assetTrack.naturalSize.width - assetTrack.naturalSize.height) / 2.0));//向上移動(dòng)取中部影相

layerTransform = CGAffineTransformScale(layerTransform, rate, rate);//放縮墩新,解決前后攝像結(jié)果大小不對(duì)稱

[layerInstrucition setTransform:layerTransform atTime:kCMTimeZero];

[layerInstrucition setOpacity:0.0 atTime:totalDuration];

//data

[layerInstructionArray addObject:layerInstrucition];

}

//get save path

NSURL *mergeFileURL = [NSURL fileURLWithPath:[self getVideoMergeFilePathString]];

//export

AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, totalDuration);

mainInstruction.layerInstructions = layerInstructionArray;

AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];

mainCompositionInst.instructions = @[mainInstruction];

mainCompositionInst.frameDuration = CMTimeMake(1, 100);

//? ? mainCompositionInst.renderSize = CGSizeMake(renderW, renderW * (sH/sW));

mainCompositionInst.renderSize = CGSizeMake(renderW, renderW * 0.75);//4:3比列

//資源導(dǎo)出

AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];

exporter.videoComposition = mainCompositionInst;

exporter.outputURL = mergeFileURL;

exporter.outputFileType = AVFileTypeMPEG4;//視頻格式MP4

exporter.shouldOptimizeForNetworkUse = YES;

[exporter exportAsynchronouslyWithCompletionHandler:^{

dispatch_async(dispatch_get_main_queue(), ^{

_videoState = VideoStateDidStartMerge;

//拋出轉(zhuǎn)換成功 代理

[self removeMovFile];//移除MOV格式視頻

});

}];

}

//計(jì)算視頻大小

- (NSInteger) getFileSize:(NSString*) path

{

path = [pathstringByReplacingOccurrencesOfString:@"file://"withString:@""];

NSFileManager* filemanager = [NSFileManagerdefaultManager];

if([filemanagerfileExistsAtPath:path]){

NSDictionary* attributes = [filemanagerattributesOfItemAtPath:patherror:nil];

NSNumber*theFileSize;

if( (theFileSize = [attributesobjectForKey:NSFileSize]) )

return[theFileSizeintValue]/1024;

else

return-1;

}

else

{

return-1;

}

}

//拉近、拉遠(yuǎn)鏡頭

- (void)changeDeviceVideoZoomFactor

{

AVCaptureDevice*backCamera = [selfgetCameraDevice:NO];

CGFloatcurrent = 1.0;

if(1.0 == backCamera.videoZoomFactor) {

current = 2.0f;

if(current > backCamera.activeFormat.videoMaxZoomFactor) {

current = backCamera.activeFormat.videoMaxZoomFactor;

}

}

NSError*error =nil;

if([backCameralockForConfiguration:&error]) {

[backCamerarampToVideoZoomFactor:currentwithRate:10];

[backCameraunlockForConfiguration];

}

else

{

NSLog(@"鎖定設(shè)備過(guò)程error窟坐,錯(cuò)誤信息:%@",error.localizedDescription);

}

}

- (AVCaptureDevice*)getCameraDevice:(BOOL)isFront

{

NSArray*cameras = [AVCaptureDevicedevicesWithMediaType:AVMediaTypeVideo];

AVCaptureDevice*frontCamera;

AVCaptureDevice*backCamera;

for(AVCaptureDevice*cameraincameras) {

if(AVCaptureDevicePositionFront== camera.position) {

frontCamera = camera;

}

elseif(AVCaptureDevicePositionBack== camera.position)

{

backCamera = camera;

}

}

if(isFront) {

returnfrontCamera;

}

returnbackCamera;

}


2海渊、自定義播放視頻

//注意:播放視頻的URL是fileURLWithPath。格式是:“file://var

- (instancetype)initVideoFileURL:(NSURL*)videoFileURL withFrame:(CGRect)frame withView:(UIView*)view

{

self= [superinit];

if(self) {

self.videoFileURL= videoFileURL;

[selfregisterNotficationMessage];

[selfinitPlayLayer:framewithView:view];

}

returnself;

}

- (void)initPlayLayer:(CGRect)rect withView:(UIView*)view

{

if(!_videoFileURL) {

return;

}

AVAsset*asset = [AVURLAssetURLAssetWithURL:_videoFileURLoptions:nil];

self.playerItem= [AVPlayerItemplayerItemWithAsset:asset];

//self.player = [AVPlayer playerWithPlayerItem:self.playerItem];

self.player= [[AVPlayeralloc]init];

self.playerLayer= [AVPlayerLayerplayerLayerWithPlayer:self.player];

[self.playersetVolume:0.0f];//靜音

[self.playerseekToTime:kCMTimeZero];

[self.playersetActionAtItemEnd:AVPlayerActionAtItemEndNone];

[self.playerreplaceCurrentItemWithPlayerItem:self.playerItem];

self.playerLayer.frame= rect;

self.playerLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;

[view.layeraddSublayer:self.playerLayer];

}

- (void)playSight

{

[self.playerItemseekToTime:kCMTimeZero];

[self.playerplay];

}

- (void)pauseSight

{

[self.playerItemseekToTime:kCMTimeZero];

[self.playerpause];

}

- (void)releaseVideoPlayer

{

[selfremoveNotificationMessage];

if(self.player) {

[self.playerpause];

[self.playerreplaceCurrentItemWithPlayerItem:nil];

}

if(self.playerLayer) {

[self.playerLayerremoveFromSuperlayer];

}

self.player=nil;

self.playerLayer=nil;

self.playerItem=nil;

self.videoFileURL=nil;

}

#pragma mark - notification message

- (void)registerNotficationMessage

{

[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(avPlayerItemDidPlayToEnd:)name:AVPlayerItemDidPlayToEndTimeNotificationobject:nil];

}

- (void)removeNotificationMessage

{

[[NSNotificationCenterdefaultCenter]removeObserver:selfname:AVPlayerItemDidPlayToEndTimeNotificationobject:nil];

}

- (void)avPlayerItemDidPlayToEnd:(NSNotification*)notification

{

if(notification.object!=self.playerItem) {

return;

}

[self.playerItemseekToTime:kCMTimeZero];

[self.playerplay];

}

有關(guān)AVFoundation的知識(shí)點(diǎn)哲鸳,還有很多臣疑。以后如果有其它的需求再做研究。

源碼地址?https://github.com/zone1026/SightRecorder

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徙菠,一起剝皮案震驚了整個(gè)濱河市讯沈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婿奔,老刑警劉巖缺狠,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異萍摊,居然都是意外死亡挤茄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門记餐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)驮樊,“玉大人,你說(shuō)我怎么就攤上這事片酝∏粝危” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵雕沿,是天一觀的道長(zhǎng)练湿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)审轮,這世上最難降的妖魔是什么肥哎? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮疾渣,結(jié)果婚禮上篡诽,老公的妹妹穿的比我還像新娘。我一直安慰自己榴捡,他們只是感情好杈女,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般达椰。 火紅的嫁衣襯著肌膚如雪翰蠢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天啰劲,我揣著相機(jī)與錄音梁沧,去河邊找鬼。 笑死蝇裤,一個(gè)胖子當(dāng)著我的面吹牛廷支,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猖辫,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼酥泞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了啃憎?” 一聲冷哼從身側(cè)響起芝囤,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辛萍,沒想到半個(gè)月后悯姊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贩毕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年悯许,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辉阶。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡先壕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谆甜,到底是詐尸還是另有隱情垃僚,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布规辱,位于F島的核電站谆棺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏罕袋。R本人自食惡果不足惜改淑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浴讯。 院中可真熱鬧朵夏,春花似錦、人聲如沸榆纽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至亮元,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唠摹,已是汗流浹背爆捞。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勾拉,地道東北人煮甥。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像藕赞,于是被迫代替她去往敵國(guó)和親成肘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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