欢迎来真孝善网,为您提供真孝善正能量书籍故事!

高效媒体播放解决方案:AVPlayer实践笔记

时间:11-03 神话故事 提交错误

老铁们,大家好,相信还有很多朋友对于高效媒体播放解决方案:AVPlayer实践笔记和的相关问题不太懂,没关系,今天就由我来为大家分享分享高效媒体播放解决方案:AVPlayer实践笔记以及的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!

做{

__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS

if (!(条件)) {

NSString *__assert_file__=[NSString stringWithUTF8String:__FILE__];

__assert_file__=__assert_file__ ? __assert_file__ : @"";

[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd

object:self file:__assert_file__

lineNumber:__LINE__ 描述:(desc), ##__VA_ARGS__];

}

__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS

而(0)

#endif 第一个参数condition是条件表达式,值为YES或NO;第二个参数desc是异常描述,通常是NSString。当条件为YES时,程序继续运行。当为NO时,抛出带有desc描述的异常消息。 NSAssert() 可以出现在程序中的任何位置。

NSAssert 和断言的区别

NSAssert 和assert 都是断言。主要区别在于,assert 在断言失败时只是终止程序,而NSAssert 会报告错误消息并将其打印出来。所以使用NSAssert就可以了,不需要使用assert。

2.NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END宏

在这两个宏之间的代码中,所有简单指针对象都被假定为非空,因此我们只需要指定那些可为空的指针。

__nullable 和__nonnull 。从字面上我们可以猜到,__nullable 意味着该对象可以为NULL 或nil,而__nonnull 意味着该对象不能为空。当我们不遵守这个规则时,编译器就会给出警告。

请注意,typedef(已定义类型的可为空属性)通常依赖于上下文,即使在审核区域中也不能假定它为非空。复杂指针类型(例如id*)必须显式指定为非null 或可为null。例如,要指定指向可空对象的非空指针,可以使用“__nullable id * __nonnull”。

我们经常使用的NSError ** 通常被假设为一个指向可空NSError 对象的可空指针。

3.合理使用NS_DESIGNATED_INITIALIZER和NS_UNAVAILABLE这两个宏

在Objective-C 中,指定构造函数主要是通过NS_DESIGNATED_INITIALIZER 宏来实现的。这里之所以使用这个宏,往往是为了告诉调用者使用这个方法来初始化(构造)类对象。

通过NS_UNAVAILABLE,可以在编译时禁用父类的方法,这也算缺陷中的完美。我们可以禁用一些不合理的类成员,以达到更好的封装效果。

如何避免使用NS_DESIGNATED_INITIALIZER 生成警告

如果子类指定了新的初始值设定项,则必须在此初始值设定项内调用父类的指定初始值设定项。并且需要重写父类的指定初始化器并将其指向子类的新初始化器。

更好的方法

如果你指定一个新的方法作为初始化器NS_DESIGNATED_INITIALIZER,大多数时候你并不希望调用者去调用父类的初始化函数,而只想通过类指定的初始化来进行初始化。这时,可以使用NS_UNAVAILABLE宏来指定系统的初始化器不可用。就是这样,那么外界想要给我初始化这个类对象的时候就可以使用指定的方法了。

例如下面的代码:

#import@interface XYView : UIView

NS_ASSUME_NONNULL_BEGIN

//使.h中的系统默认初始化程序不可用(NS_UNAVAILABLE)

- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;

- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;

//指定该方法为初始化构造函数,外界可以调用该方法来初始化对象。

- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag NS_DESIGNATED_INITIALIZER;

NS_ASSUME_NONNULL_END

@结尾

//.m 实现

- (instancetype)initWithFrame:(CGRect)frame {

NSAssert(NO, nil);

@抛出零;

}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

NSAssert(NO, nil);

@抛出零;

}

//当.m实现指定的初始化方法时,不要忘记让当前类调用父类的初始化方法。

- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag {

if (self=[超级initWithFrame:frame]) {

}

返回自我;

}此时外界给我初始化这个对象时,系统就不会提示声明NS_UNAVAILABLE宏的两个初始化方法了。你可以使用我指定的方法进行初始化。当然,你也可以将init方法设置为disabled。使用,如下图

Snip20161111_1.png4.iOS相机使用-UIImagePickerController

1、来源类型: 该参数用于决定是否调用相机或图片库。如果是UIImagePickerControllerSourceTypeCamera,则调用相机。如果是UIImagePickerControllerSourceTypePhotoLibrary则调用图片库。如果是UIImagePickerControllerSourceTypeSavedPhotosAlbum,则调用iOS设备中的胶片相机图片。

2.Media types:用于指定拍照时是拍摄静态图片还是视频。 kUTTypeImage 表示静态图片,kUTTypeMovie 表示视频。

请注意,KUTTYpeImage 和kUTTypeMovie 常量是MobileCoreServices 框架中的CFStringRef 类型常量。他们需要导入#import头文件并将格式转换为NSString。

3.编辑控件:用于指定是否可编辑。将allowsEditing属性设置为YES表示可编辑,NO表示不可编辑,并设置拍照或选择相册中的照片后是否跳转到编辑模式进行图片裁剪。仅当showsCameraControls属性为YES时才有效imagepicker.allowsEditing=YES;

4.代理方法:- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;成功获取视频或相册后回调

比如下面的代码

UIImagePickerController *myImagePickerController=[[UIImagePickerController alloc] init];

myImagePickerController.sourceType=UIImagePickerControllerSourceTypePhotoLibrary;

//让相机只拍照不拍照,设置mediaTypes为kUTTypeMovie

myImagePickerController.mediaTypes=

[[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil];

myImagePickerController.delegate=self;

myImagePickerController.editing=NO;

[自我呈现ViewController:myImagePickerController动画:YES完成:nil];

4. AVAsset

AVFoundation为我们提供了一个非常大的可扩展框架,通过它我们可以捕获、组合、播放和处理媒体资源。 AVAsset是AVFoundation框架中非常重要的一个类

1.什么是AVAsset? AVAsset 是一个抽象类,也是一个不可变的类,它定义了媒体资源混合和呈现的方式。它允许我们的开发人员提供一种简单且统一的方式来处理基于时间的媒体。它不是媒体资源,但可以用作基于时间的媒体的容器。

2.创建方法当我们想要为某个媒体资源创建一个AVAsset对象时,我们可以通过URL来初始化它。 URL 可以是本地资源或网络资源。

AVAsset *asset=[AVAsset asetWithURL:assetUrl];```

通过asetWithURL方法创建时,实际上创建的是AVAsset子类AVUrlAsset的实例,而AVAsset是一个抽象类,不能直接实例化。

通过AVUrlAsset,我们可以创建一个带有选项的资产,以提供更精确的持续时间和计时信息。

- ###5.AVPlayer 播放视频

在iOS开发中,播放视频通常有两种方式:一种是使用MPMoviePlayerController(需要导入MediaPlayer.Framework),另一种是使用AVPlayer。

简而言之,这两个类的区别在于MPMoviePlayerController使用起来更简单,功能不如AVPlayer强大,而AVPlayer使用起来稍微麻烦一些,但功能更强大。

仅仅使用AVPlayer类无法显示视频。视频层必须添加到AVPlayerLayer中才能显示视频。

- 播放资产

1. 播放器是控制资源播放的对象,例如开始、结束、寻找指定时间等。您可以使用AVPlayer 播放单个资源,使用AVQueuePlayer 播放多个连续资源。

2. 播放器为您提供播放信息。如有需要,可以通过播放器状态同步显示在界面上。您还可以直接将播放器的输出显示到指定的动画层(AVPlayerLayer 或AVSynchronizedLayer)。多个图层:您可以创建多个AVPlayerLayer 对象,但只有最近创建的图层才会显示视频屏幕。 ````

3.虽然播放了资源,但无法直接将资源传递给AVPlayer对象。您应该向AVPlayer 提供AVPlayerItem 对象。播放器项目管理与其关联的资产。播放器项目包括播放器项目轨道-(AVPlayerItemTrack 对象,表示资产中的轨道)

- 使用AVPlayerLayer播放视频文件。步骤如下:

1.使用AVPlayerLayer配置视图

2. 创建一个AVPlayer

3.使用视频文件创建AVPlayerItem对象,并使用kvo观察其状态。

4. 当接收到的项目的状态变为可播放时,播放按钮被启用。

5. 播放,结束后将播放头置于起始位置。

-###6。 CALayer - contentGravity 属性

当我们使用Cocoa视图时,我们必须继承NSView或UIView并重载函数drawRect:来显示任何内容。但CALayer实例可以直接使用,无需子类化。由于CALayer是一个键值编码兼容的容器类,可以在实例中存储任意值,因此可以完全避免子类实例化。

- 向CALayer提供内容

您可以通过以下任意方式指定CALayer 实例的内容:

1. 使用包含图像内容的CGImageRef显式设置图层的contents属性。

2. 指定提供或重绘内容的委托。

3、继承CALayer类,并重载显示功能。

- 1设置内容属性

可以通过将contents属性的值指定为CGImageRef来指定图层的图像内容。这个操作可以在图层创建时或任何其他时间对其他实体进行,例如CALayer *layer=[CALayer layer];

图层位置=CGPointMake(50.0, 50.0);

层.bounds=CGRectMake(0, 0, 200, 200);

layer.contents=(__bridge id _Nullable)([[UIImage imageNamed:@"image"] CGImage]);```修改图层内容的位置

CALayer 属性contentsGravity 允许您修改图层内容图像在图层边界内容处的位置或缩放值。默认情况下,内容图像完全填充图层的边界,忽略自然图像的长宽比。

使用contentsGravity位置常量,您可以指定图像位于图层的任何边界,例如图层的角点或图层边界的中心。但是,当您使用位置常量时,contentsCenter 属性将被忽略。

“层的坐标系”标识支持的内容位置及其相应的常量。

Layer_contentsgravity1.jpg

通过将contentGravity 属性设置为另一个常量。图层的内容图像可以向上或向下拉伸。当使用任何其他调整大小常量时,contentsCenter 属性仅影响内容图像。

7.AVPlayer视频播放相关

1.Video Gravity 视频播放时的拉伸方式

视频播放时的拉伸样式.jpg

苹果定义的三个常量,用于视频播放过程中的拉伸方法

AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspect NS_AVAILABLE(10_7, 4_0);

AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspectFill NS_AVAILABLE(10_7, 4_0);

AVF_EXPORT NSString *const AVLayerVideoGravityResize NS_AVAILABLE(10_7, 4_0);AVLayerVideoGravityResize, //非均匀模式。两个尺寸完全填满整个视图区域

AVLayerVideoGravityResizeAspect, //等比例填充,直到一维到达区域边界

AVLayerVideoGravityResizeAspectFill, //等比例填充,直至填满整个视图区域,并裁剪掉一部分一维。例如,设置playerLayer如下

AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:player];

playerLayer.contentsGravity=AVLayerVideoGravityResizeAspect;

2.actionAtItemEnd

@property (非原子) AVPlayerActionAtItemEnd actionAtItemEnd;这是AVPlayerActionAtItemEnd的枚举类型,表示当item的播放到达结束时间时,播放器应该执行的动作

定义如下

typedef NS_ENUM(NSInteger, AVPlayerActionAtItemEnd)

{

AVPlayerActionAtItemEndAdvance=0, //结束前进

AVPlayerActionAtItemEndPause=1, //结束暂停

AVPlayerActionAtItemEndNone=2, //不结束

};

3. 如何控制快进和快退

- (void)seekToTime:(CMTime)时间

toleranceBefore:(CMTime)toleranceBefore、toleranceAfter:(CMTime)toleranceAfter;使用此方法查找当前播放器项的指定时间。结果时间将在[时间容差之前,时间+容差之后]范围内,并且可能与指定时间不同以提高效率。对于toleranceBefore和toleranceAfter,需要kCMTimeZero来请求样本精确查找,这可能会产生额外的解码延迟。使用beforeTolerance 消息:kCMTimePositiveInfinity 和afterTolerance:kCMTimePositiveInfinity 传递此方法与消息eekToTime 直接相同:

例如,[self.playereekToTime:time容忍Before:kCMTime零容忍After:kCMTimeZero];

8.CMTimeMake 和 CMTimeMakeWithSeconds

CMTime是专门用来标识电影时间的结构体。通常使用以下两个函数来创建CMTime

苹果定义CMTime源代码:

类型定义结构

{

CMTimeValue 值; //CMTime 值: 值/时间刻度=秒

CMTimeScale 时间刻度; //每秒帧数

CMTimeFlags 标志; /*! @field flags 标志,例如。 kCMTimeFlags_Valid、kCMTimeFlags_PositiveInfinity 等*/

CMTimeEpoch 纪元; /* 对由于循环、多项排序等原因而实际不同的相同时间戳进行时间微分。在比较周期时将使用:较大的周期出现在较小的周期之后。然而,加法/减法只能在单个纪元内完成,因为纪元长度可能未知/可变*/

CMTime;第一种创建CTTime的方式

CMTime CMTimeMake (

int64_t value, //表示当前视频播放到的帧号

int32_t timescale //每秒帧数

);```

注意:```CMTimeMake(time, timeScale)```,第一个参数time指的是时间(不是秒),而要将时间转换为秒,需要看第二个参数timeScale,timeScale指的是1秒需要由几个帧来构造(可以看成fps的每秒帧数),因为要表达的真实时间是time/timeScale即秒

我经常在其他开源项目中看到CMTime是这样定义的

```CMTime 第一帧=CMTimeMake(1,10);

CMTime 最后一帧=CMTimeMake(10, 10);```

上面的代码可以理解为:视频的fps(帧率)为10,firstframe为视频的第一帧,时间为0.1秒,lastframe为视频的第10帧,时间为1第二。

或者另一种写法CMTime curFrame=CMTimeMake(frame, 帧速率)

第二种创建CMTime的方式CMTime CMTimeMakeWithSeconds(

Float64秒, //前几秒的截图是当前视频播放的帧号的具体时间。

int32_t PreferredTimeScale //首选时间尺度"每秒帧数"

);

- ###9.AVAsset、AVMutableComposition 视频裁剪示例

这些相关的类有些抽象。使用代码合成视频其实和使用Video/Vegas/Final Cut Pro等软件合成视频的过程类似。首先我们来了解一下这类软件的一些相关知识: 一个工程文件包含有很多个轨道,比如音轨1、音轨2、音轨3、视频轨道1、视频轨道2等,每个轨道有很多材料。对于每个视频素材,都可以进行缩放、旋转等操作,并将素材库中的视频拖到轨道上,就会分为两个轨道:视频轨道和音频轨道。

- 以下是这些软件中的一些术语类来比较这些类: AVAsset:材料库中的材料;

AVAssetTrack:素材的轨迹;

AVMutableComposition:用于合成视频的项目文件;

AVMutableCompositionTrack:项目文件中的轨道,包括音轨、视频轨道等,可以在其中插入各种相应的素材;

AVMutableVideoCompositionLayerInstruction:视频轨道中的一个视频,可以进行缩放、旋转等操作;

AVMutableVideoCompositionInstruction:一个视频轨道,包含该轨道上的所有视频素材;

AVMutableVideoComposition:管理所有视频轨道并可以确定最终视频的大小。这里需要进行裁剪;

AVAssetExportSession:配置渲染参数并渲染。 ````

接下来,用这个类比来裁剪视频:

1.将素材拖入素材库

AVAsset *asset=[AVAsset assetWithURL:outputFileURL];

AVAssetTrack *videoAssetTrack=[[asset trackWithMediaType:AVMediaTypeVideo]objectAtIndex:0];//素材的视频轨道

AVAssetTrack *audioAssertTrack=[[asset trackWithMediaType:AVMediaTypeAudio]objectAtIndex:0];//素材的音频轨道

2、将素材的视频插入视频轨道,音频插入音轨

AVMutableComposition *composition=[AVMutableCompositioncomposition];//这是项目文件

AVMutableCompositionTrack *videoCompositionTrack=[组合addMutableTrackWithMediaType:AVMediaTypeVideo 首选项

rredTrackID:kCMPersistentTrackID_Invalid];//视频轨道 [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];//在视频轨道插入一个时间段的视频 AVMutableCompositionTrack *audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];//音频轨道 [audioCompositionTrack insertTimeRange: CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:audioAssertTrack atTime:kCMTimeZero error:nil];//插入音频数据,否则没有声音 3.裁剪视频,就是要将所有视频轨进行裁剪,就需要得到所有的视频轨,而得到一个视频轨就需要得到它上面所有的视频素材 AVMutableVideoCompositionLayerInstruction *videoCompositionLayerIns = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoAssetTrack]; [videoCompositionLayerIns setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];//得到视频素材(这个例子中只有一个视频) AVMutableVideoCompositionInstruction *videoCompositionIns = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; [videoCompositionIns setTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration)];//得到视频轨道(这个例子中只有一个轨道) AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; videoComposition.instructions = @[videoCompositionIns]; videoComposition.renderSize = CGSizeMake(...);//裁剪出对应的大小 videoComposition.frameDuration = CMTimeMake(1, 30); 4.导出 AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality]; exporter.videoComposition = videoComposition; // 视频输出的地址 exporter.outputURL = [NSURL fileURLWithPath:_outputFilePath isDirectory:YES]; // 输出文件的类型 exporter.outputFileType = AVFileTypeMPEG4; exporter.shouldOptimizeForNetworkUse = YES; // 剪辑视频:注意它是一个异步操作,outputFileType属性的设置 [exporter exportAsynchronouslyWithCompletionHandler:^{ if (exporter.error) { //... }else{ //... } }];``` - ###10.视频导出选项AVF_EXPORT NSString *const AVAssetExportPresetLowQuality NS_AVAILABLE(10_11, 4_0); AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality NS_AVAILABLE(10_11, 4_0); AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality NS_AVAILABLE(10_11, 4_0);``` 这些导出选项可用于生成具有适合于设备的视频大小的电影文件。导出不会将视频从较小的大小缩放。视频将使用压缩H.264和音频将使用AAC进行压缩。 + (NSArray*)exportPresetsCompatibleWithAsset:(AVAsset *)asset;AVAssetExportSession导出视频的状态枚举类型AVAssetExportSessionStatus typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) { AVAssetExportSessionStatusUnknown, // 导出状态未知 AVAssetExportSessionStatusWaiting, // 导出等待状态 AVAssetExportSessionStatusExporting, // 正在导出状态 AVAssetExportSessionStatusCompleted, // 已导出完成状态 AVAssetExportSessionStatusFailed, // 导出失败状态 AVAssetExportSessionStatusCancelled // 已取消导出状态 };注意: 导出视频的方法是异步执行的,在导出完成后回到主线程保存视频到相册中,如下 // 导出视频 [self.exportSession exportAsynchronouslyWithCompletionHandler:^{ switch ([self.exportSession status]) { case AVAssetExportSessionStatusFailed NSLog(@"Export failed: %@", [[self.exportSession error] localizedDescription]); break; case AVAssetExportSessionStatusCancelled: NSLog(@"Export canceled"); break; default: NSLog(@"NONE"); dispatch_async(dispatch_get_main_queue(), ^{ NSURL *movieUrl = [NSURL fileURLWithPath:self.tempVideoPath]; UISaveVideoAtPathToSavedPhotosAlbum([movieUrl relativePath], self,@selector(video:didFinishSavingWithError:contextInfo:), nil); }); break; } }];

11. AVAssetImageGenerator

AVFoundation生成视频缩略图主要靠如下两个类

关于高效媒体播放解决方案:AVPlayer实践笔记,的介绍到此结束,希望对大家有所帮助。

用户评论

嗯咯

想了解一下AVPlayer的使用方法,这篇文章能帮我吗?

    有12位网友表示赞同!

哥帅但不是蟋蟀

我之前用过AVPlayer,但这篇笔记感觉很全面,可以让我复习巩固一下知识点。

    有9位网友表示赞同!

浮光浅夏ζ

一直想要学习一下视频播放,这篇笔记正好合适。

    有11位网友表示赞同!

一生荒唐

看了标题,应该是和iOS开发有关的,对这一块挺感兴趣的,打算好好看看这篇文章。

    有18位网友表示赞同!

命该如此

最近在做一些视频相关的项目,希望能从这些笔记中学到一些相关技巧。

    有19位网友表示赞同!

寻鱼水之欢

AVPlayer的使用场景很多,学习一下总是好过不去用才知道啊!

    有13位网友表示赞同!

冷眼旁观i

感觉这个笔记涵盖了比较深入的知识点,比一般教程更专业。

    有16位网友表示赞同!

心脏偷懒

标题看着很直观,不知道文章里有什么具体的例子或代码示例。

    有7位网友表示赞同!

夏至离别

对iOS开发一直感兴趣,这篇文章可以让我进一步了解一下视频播放的相关技术。

    有10位网友表示赞同!

淡写薰衣草的香

最近在使用AVPlayer遇到一些问题,希望这篇笔记能够提供解决办法。

    有7位网友表示赞同!

小清晰的声音

看标题感觉像是学习材料,希望能帮到我!

    有7位网友表示赞同!

素衣青丝

平时很少接触AVPlayer,想来了解一下它的使用方法和特点。

    有15位网友表示赞同!

莫名的青春

这篇文章会不会覆盖所有关于AVPlayer的使用方法?

    有19位网友表示赞同!

凝残月

iOS开发中使用AVPlayer还是挺重要的,看来要找一些相关的学习资源了。

    有16位网友表示赞同!

妄灸

文章内容应该比较详细的介绍了AVPlayer的使用原理吧?

    有15位网友表示赞同!

生命一旅程

希望能从笔记中找到一些解决实际问题的例子应用!

    有19位网友表示赞同!

灬一抹丶苍白

学习视频播放技术一直是我的目标,希望能从这篇笔记里得到启迪。

    有6位网友表示赞同!

羁绊你

我想要深入理解AVPlayer的运作机制,这篇文章能满足我的需求吗?

    有8位网友表示赞同!

暮染轻纱

标题很有吸引力,感觉文章内容一定很精彩!

    有15位网友表示赞同!

还未走i

希望笔记能提供一些实用技巧和经验分享,方便实际项目开发。

    有16位网友表示赞同!

【高效媒体播放解决方案:AVPlayer实践笔记】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活