AVPlayer实现简单播放器
来源:互联网 发布:淘宝店铺海报尺寸多少 编辑:程序博客网 时间:2024/05/01 00:31
在我们之前的项目,视频的播放都是使用MPMoviePlayerViewController(包含了播放的UI,并封装好了播放的一些函数),但是苹果的官网表示从IOS9版本开始,MPMoviePlayerViewController被弃用了,推荐使用AVPictureInPictureController或者AVPlayerViewController。
但是一直使用苹果封装的ViewController可控性依旧比较差,如:
(1)播放状态无法获得;
(2)置于后台和唤醒时,在不同设备上视频播放展现状态不同;
(3)视频播放进度与画面不同步;
(4)播放的声音和画面不同步;
(5)无法直接加其他UI,如:分享按钮等。
基于以上几点考虑,我决定直接用AVPlayer直接实现一个播放器,完成一些简单的播放功能。
基本功能:
(1)进度条、视频播放画面、播放时间同步;
(2)可以播放和暂停,且两种状态有不同UI切换;
(3)进度条可以拖动,且视频播放画面同步;
(4)点击关闭按钮可以销毁播放器;
(5)增加了AutoLayout布局约束
(6)视频的横竖屏切换播放与设备的横竖屏切换同步。
(7)一些小细节,如:进度条拖拽、点击、抬起时,播放/暂停按钮UI的变化;置于后台和唤醒,重新设置视频播放状态等。
代码如下:
头文件:ViewController.h
#import <UIKit/UIKit.h>#import <AVFoundation/AVFoundation.h>@interface ViewController : UIViewController@property (nonatomic,strong)AVPlayer *avPlayer;@property (nonatomic,strong)AVPlayerItem *avPlayerItem;@property (nonatomic,strong)AVPlayerLayer *avPlayerLayer;//装载视频layer、进度条、时间、时长等UI的容器@property (nonatomic,strong)UIView *videoContainer;//进度条UI@property (nonatomic,strong)UISlider *progress;//当前播放时间UI@property (nonatomic,strong) UILabel *startLabel;//视频总时长UI@property (nonatomic,strong) UILabel *endLabel;//关闭按钮@property (nonatomic,strong) UIButton *closeButton;//控制播放和暂停的按钮@property (nonatomic,strong) UIButton *playButton;//当前播放时间@property (nonatomic,assign) float playheadTime;//是否处于播放状态@property (nonatomic,assign) BOOL isPlaying;//播放帧的监视器@property (nonatomic,strong) id playTimeObserver;@end
实现:ViewController.m<span style="font-size:14px;">#import "ViewController.h"@interface ViewController ()@endstatic NSBundle *playerBundle;NSString * const XAdVideoStatusKey = @"status";@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; NSString *urlString = @"要播放的视频地址 http://...."; [self configPlayerControllerWithURL:urlString]; }- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning];}#pragma mark - public- (void) configPlayerControllerWithURL:(NSString *)urlString{ [self initBundle]; [self setProgressRefProperties]; [self showUI]; NSURL *url = [NSURL URLWithString:urlString]; [self setupPlayerWithURL:url]; }/* * 重头播放 */- (void)play{ if (self.avPlayer) { self.isPlaying = YES; [self.avPlayer play]; }}/* * 暂停 */- (void)pause{ if (self.avPlayer) { [self.avPlayer pause]; self.isPlaying = NO; } }/* * 销毁对象 */- (void)cancel { if (self.avPlayer) { [self.avPlayer pause]; } [self removeUIView]; [self removePlayerItemObserver]; [self removePlayerObserver]; [self destroyAllProperties]; }/* * 从某一帧播放 */- (void)playFromTime:(double)time{ NSLog(@"playFromTime = %f",time); if (self.avPlayer) { [self seekToTime:time]; [self.avPlayer play]; self.isPlaying = YES; }}/* * 在某一帧暂停 */- (void)pauseOnTime:(double)time{ NSLog(@"pauseOnTime = %f",time); if (self.avPlayer) { [self seekToTime:time]; [self.avPlayer pause]; self.isPlaying = NO; }}#pragma mark - action event/* * 点击播放或暂停按钮,控制播放状态 */- (void) onClickPlayOrPause{ if (self.isPlaying) { [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_play"] forState:UIControlStateNormal]; [self pauseOnTime:self.playheadTime]; }else{ [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_pause"] forState:UIControlStateNormal]; if ([self playerReachedEnd]) { [self playFromTime:0]; }else{ [self playFromTime:self.playheadTime]; } }}/* * 关闭按钮触发,销毁所有player和UI相关对象 */- (void) onClickClose{ [self cancel];}#pragma mark - add observer/* * 播放帧的观察者 */- (void)addPlayerObserver{ // add 应用置于后台、唤醒、屏幕旋转的监听器 (如果不需要此功能可以不添加) [self addAppObserver]; CMTime interval = CMTimeMakeWithSeconds(0.1, NSEC_PER_USEC); __weak ViewController *selfWeak = self; self.playTimeObserver = [self.avPlayer addPeriodicTimeObserverForInterval:interval queue:NULL usingBlock:^(CMTime time) { // 当前的播放时间 float currentTime = (float)CMTimeGetSeconds(time); // 刷新进度条以及时间Label的显示 [selfWeak refreshSlide:currentTime]; }];}- (void)addPlayerItemObserver{ // 监测播放的状态(已就绪 / 失败 ) [self.avPlayerItem addObserver:self forKeyPath:XAdVideoStatusKey options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.avPlayerItem]; }- (void)addAppObserver{ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; // 应用从后台唤醒 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; // 应用置于后台 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil]; // 屏幕旋转 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];}#pragma mark - remove observer- (void)removePlayerObserver{ [self removeAppObserver]; if (self.avPlayer && self.playTimeObserver) { [self.avPlayer removeTimeObserver:self.playTimeObserver]; }}- (void)removePlayerItemObserver{ [self.avPlayerItem removeObserver:self forKeyPath:XAdVideoStatusKey context:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.avPlayerItem];}- (void)removeAppObserver{ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];}#pragma mark - observer & notification- (void)appDidBecomeActive:(NSNotification *)notification{ //后台唤醒继续播放 [self playFromTime:self.playheadTime];}- (void)appDidEnterBackground:(NSNotification *)notification{ //置于后台停止播放 [self pauseOnTime:self.playheadTime];}- (void)orientationChanged:(NSNotification *)notification{ //屏幕旋转重新布局UI [self reloadVideoContainerView];}/* * 监测player的状态 */- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ if (object == self.avPlayerItem && [keyPath isEqualToString:XAdVideoStatusKey]) { if (self.avPlayerItem.status == AVPlayerItemStatusFailed) { NSLog(@"AVPlayerItemStatusFailed."); } else if (self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay) { if (self.videoContainer) { self.avPlayerLayer.frame = [self.videoContainer bounds]; NSLog(@"add playerLayer to video container."); [self.videoContainer.layer insertSublayer:self.avPlayerLayer atIndex:0]; [self play]; } } }}- (void) playerItemDidReachEnd:(NSNotification *)notification{ // 播放结束,改变播放暂停按钮的状态 [self changePlayOrPauseUI]; }/* * 刷新进度条以及相关UI */- (void)refreshSlide:(float) time{ CMTime duration = self.avPlayer.currentItem.asset.duration; self.playheadTime = time; int currentSeconds = (int)(self.playheadTime); int fullDuration = (int)(duration.value/duration.timescale); self.progress.minimumValue = 0; self.progress.maximumValue = fullDuration; // 刷新进度条的进度 self.progress.value = currentSeconds; // 刷新时间的显示 NSMutableAttributedString *startLabelText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%02d:%02d",currentSeconds/60,currentSeconds%60]]; NSMutableAttributedString *endLabelText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%02d:%02d",fullDuration/60,fullDuration%60]]; [startLabelText addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0,startLabelText.length)]; [endLabelText addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0,endLabelText.length)]; self.startLabel.attributedText = startLabelText; self.endLabel.attributedText = endLabelText;}/* * 手动滑动进度条继续播放或者暂停 */- (void) progressChanged{ if ([self isPlaying]) { // 如果之前是播放状态,那么继续播放 [self playFromTime:self.progress.value]; }else{ // 如果之前是是暂停状态,那么保持暂停 [self pauseOnTime:self.progress.value]; } }/* * 进度条被拖拽,改变播放暂停按钮的状态 */- (void) progressDragInside{ NSLog(@"progressDragInside value:%f",self.progress.value); [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_play"] forState:UIControlStateNormal];}/* * 进度条被按住,改变播放暂停按钮的状态 */- (void) progressTouchDown{ NSLog(@"progressTouchDown value:%f",self.progress.value); [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_play"] forState:UIControlStateNormal]; }/* * 进度条按住后松开,改变播放暂停按钮的状态 */- (void)progressTouchUpInside{ NSLog(@"progressTouchUpInside value:%f",self.progress.value); if ([self isPlaying]) { [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_pause"] forState:UIControlStateNormal]; }else{ [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_play"] forState:UIControlStateNormal]; }}#pragma mark - private/* * 屏幕旋转后刷新frame的大小 */- (void)reloadVideoContainerView{ CGPoint currentOrigin = [UIScreen mainScreen].applicationFrame.origin; CGSize currentSize = [self getScreenSize]; CGRect currentScreenFrame = CGRectMake(currentOrigin.x, currentOrigin.y, currentSize.width, currentSize.height); self.videoContainer.frame = currentScreenFrame; self.avPlayerLayer.frame = self.videoContainer.bounds;}/* * 进度条相关UI的布局(如果无此需求可忽略,但是在初始化时的CGRect要设置好) * 布局顺序:在app的底部 ==> 播放/暂停按钮、播放时间、进度条、播放时长 * 在app的左上角 ==> 关闭按钮 */- (void)addConstraint{ _closeButton.translatesAutoresizingMaskIntoConstraints = NO; _progress.translatesAutoresizingMaskIntoConstraints = NO; _playButton.translatesAutoresizingMaskIntoConstraints = NO; _startLabel.translatesAutoresizingMaskIntoConstraints = NO; _endLabel.translatesAutoresizingMaskIntoConstraints = NO; // Align play button left to video container NSLayoutConstraint* playbuttonLeftConstraint = [NSLayoutConstraint constraintWithItem:_playButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.videoContainer attribute:NSLayoutAttributeLeading multiplier:1.0f constant:10.0f]; // Align play button buttom to video container NSLayoutConstraint* playbuttonButtomConstraint = [NSLayoutConstraint constraintWithItem:_playButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.videoContainer attribute:NSLayoutAttributeBottom multiplier:1.0f constant: -5.0f]; // Align start label left to play button NSLayoutConstraint* startLabelLeftConstraint = [NSLayoutConstraint constraintWithItem:_startLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.playButton attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:10.0f]; // Align start label centerY to video container NSLayoutConstraint* startLabelCenterYConstraint = [NSLayoutConstraint constraintWithItem:_startLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.progress attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]; // Align progress slider left to start label NSLayoutConstraint* progressLeftConstraint = [NSLayoutConstraint constraintWithItem:_progress attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.startLabel attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:10.0f]; // Align progress slider right to end label NSLayoutConstraint* progressRightConstraint = [NSLayoutConstraint constraintWithItem:_progress attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.endLabel attribute:NSLayoutAttributeLeading multiplier:1.0f constant:-10.0f]; // Align progress centerY to play button NSLayoutConstraint* progressCenterYConstraint = [NSLayoutConstraint constraintWithItem:_progress attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.playButton attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]; // Align end label right to video contrainer NSLayoutConstraint* endLabelRightConstraint = [NSLayoutConstraint constraintWithItem:_endLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.videoContainer attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-10.0f]; // Align end label centerY to video container NSLayoutConstraint* endLabelCenterYConstraint = [NSLayoutConstraint constraintWithItem:_endLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.progress attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]; // Align close button left to video container NSLayoutConstraint* closebuttonLeftConstraint = [NSLayoutConstraint constraintWithItem:_closeButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.videoContainer attribute:NSLayoutAttributeLeading multiplier:1.0f constant:3.0f]; // Align close button top to video container NSLayoutConstraint* closebuttonTopConstraint = [NSLayoutConstraint constraintWithItem:_closeButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.videoContainer attribute:NSLayoutAttributeTop multiplier:1.0f constant:3.0f]; [self.videoContainer addConstraints:@[playbuttonLeftConstraint, startLabelLeftConstraint, progressLeftConstraint, endLabelRightConstraint,progressRightConstraint,playbuttonButtomConstraint,closebuttonLeftConstraint,closebuttonTopConstraint,playbuttonButtomConstraint,startLabelCenterYConstraint,endLabelCenterYConstraint,progressCenterYConstraint]]; }/* * 获取屏幕大小,IOS8以后屏幕宽高是真实宽高,IOS8之前需要翻转 */- (CGSize) getScreenSize{ CGSize size; CGSize screenSize = [UIScreen mainScreen].applicationFrame.size; if ([self isLaterThanIOS8]) { size = screenSize; NSLog(@"ios8 or ios9, width = %f,height = %f",size.width,size.height); }else{ if ( UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) { // Landscape Orientation, reverse size values size.width = screenSize.height; size.height = screenSize.width; } else { // portrait orientation, use normal size values size.width = screenSize.width; size.height = screenSize.height; } NSLog(@" not ios8 or ios9, width = %f,height = %f",size.width,size.height); } return size;}/* * 初始化avplayerItem、avplayer、avplayerLayer */- (void)setupPlayerWithURL:(NSURL *)URL{// dispatch_async(dispatch_get_global_queue(0, 0), ^{ self.avPlayerItem = [AVPlayerItem playerItemWithURL:URL]; [self addPlayerItemObserver]; self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; [self addPlayerObserver]; self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; [self.avPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; self.avPlayerLayer.needsDisplayOnBoundsChange = YES;// }); }- (void) showUI{ [self addConstraint]; [self.view addSubview:self.videoContainer]; [self.videoContainer addSubview:self.progress]; [self.videoContainer addSubview:self.startLabel]; [self.videoContainer addSubview:self.endLabel]; [self.videoContainer addSubview:self.playButton]; [self.videoContainer addSubview:self.closeButton]; }- (void)setProgressRefProperties{ // 获取不包括状态栏的屏幕frame CGRect appViewFrame = [ UIScreen mainScreen ].applicationFrame; _videoContainer = [[UIView alloc]initWithFrame:appViewFrame]; self.videoContainer.backgroundColor = [UIColor blackColor]; // 初始化进度条(尺寸没有生效,因为后面用了布局约束) _progress = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, 100, 5)]; // 设置进度条播放按钮的图片 [_progress setThumbImage:[self getImageFromBundle:playerBundle imageName:@"player_dot"] forState:UIControlStateNormal]; [_progress setThumbImage:[self getImageFromBundle:playerBundle imageName:@"player_dot"] forState:UIControlStateHighlighted]; // 设置进度条已播放和未播放部分的颜色 [_progress setMinimumTrackTintColor:[UIColor whiteColor]]; [_progress setMaximumTrackTintColor:[UIColor darkGrayColor]]; // 设置进度条的事件函数 [self.progress addTarget:self action:@selector(progressChanged) forControlEvents:(UIControlEventValueChanged)]; [self.progress addTarget:self action:@selector(progressDragInside) forControlEvents:(UIControlEventTouchDragInside)]; [self.progress addTarget:self action:@selector(progressTouchDown) forControlEvents:(UIControlEventTouchDown)]; [self.progress addTarget:self action:@selector(progressTouchUpInside) forControlEvents:(UIControlEventTouchUpInside)]; // 初始化进度条(尺寸没有生效,因为后面用了布局约束) _startLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 15)]; _endLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 15)]; // 设置时间Lable字体样式和颜色 NSMutableAttributedString *startLabelText = [[NSMutableAttributedString alloc] initWithString:@"00:00"]; NSMutableAttributedString *endLabelText = [[NSMutableAttributedString alloc] initWithString:@"00:00"]; [startLabelText addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0,startLabelText.length)]; [endLabelText addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0,endLabelText.length)]; self.startLabel.attributedText = startLabelText; self.endLabel.attributedText = endLabelText; //初始化播放暂停按钮,设置图片 _playButton = [[UIButton alloc] initWithFrame:CGRectMake(10, 50, 20, 20)]; [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_pause"] forState:UIControlStateNormal]; [_playButton addTarget:self action:@selector(onClickPlayOrPause) forControlEvents:UIControlEventTouchUpInside]; //初始化关闭按钮,设置相应图片 _closeButton = [[UIButton alloc]initWithFrame:CGRectMake(50, 50, 20, 20)]; [_closeButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_close"] forState:UIControlStateNormal]; [_closeButton addTarget:self action:@selector(onClickClose) forControlEvents:UIControlEventTouchUpInside]; }/* * 改变播放或暂停按钮的状态 */- (void) changePlayOrPauseUI{ if (self.isPlaying) { [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_play"] forState:UIControlStateNormal]; self.isPlaying = NO; }else{ [_playButton setImage:[self getImageFromBundle:playerBundle imageName:@"player_pause"] forState:UIControlStateNormal]; self.isPlaying = YES; }}/* * 判断播放是否结束 */- (BOOL)playerReachedEnd { CMTime duration = self.avPlayer.currentItem.asset.duration; CMTime currentTime = self.avPlayer.currentItem.currentTime; // CMTime包含value和timescale两个属性,比值用来获取真正的播放时间 int currentSeconds = (int)(currentTime.value/currentTime.timescale); int fullDuration = (int)(duration.value/duration.timescale); return (currentSeconds == fullDuration) ? YES : NO;}/* * 跳到某一播放帧 */- (void)seekToTime:(double)time{ if (time >= 0) { CMTime timeStruct = CMTimeMake(time * 1000, 1000); [self.avPlayer seekToTime:timeStruct toleranceBefore:kCMTimeZero toleranceAfter:kCMTimePositiveInfinity]; }}/* * 获取资源包 */- (void) initBundle{ NSString * bundlePath = [[ NSBundle mainBundle] pathForResource: @"playicon" ofType :@"bundle"]; playerBundle = [NSBundle bundleWithPath:bundlePath];}/* * 获取资源包中的图片资源 */- (UIImage *)getImageFromBundle:(NSBundle *) bundle imageName:(NSString *)imageName{ NSString *imagePath = [playerBundle pathForResource:imageName ofType:@"png"]; UIImage *image = [[UIImage alloc]init]; if (imagePath) { image = [UIImage imageWithContentsOfFile:imagePath]; } return image;}/* * 是否系统版本高于IOS8 */- (BOOL) isLaterThanIOS8{ return ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"8."] || [[[UIDevice currentDevice] systemVersion] hasPrefix:@"9."]);}- (void)destroyAllProperties{ _avPlayer = nil; _avPlayerItem = nil; _avPlayerLayer = nil; _progress = nil; _startLabel = nil; _endLabel = nil; _closeButton = nil; _playButton = nil; _playTimeObserver = nil;}- (void) removeUIView{ if (self.avPlayerLayer) { [self.avPlayerLayer removeFromSuperlayer]; } if (self.videoContainer) { [self.videoContainer removeFromSuperview]; } if (self.progress) { [self.progress removeFromSuperview]; } if (self.closeButton) { [self.closeButton removeFromSuperview]; } if (self.startLabel) { [self.startLabel removeFromSuperview]; } if (self.endLabel) { [self.endLabel removeFromSuperview]; } if (self.playButton) { [self.playButton removeFromSuperview]; } }- (void)dealloc{ [self cancel];}@end</span>
源码地址:
https://github.com/MsSmart/VideoPlayerDemo.git
未解决的问题:
(1)视频播放进度与画面不同步
(2)播放的声音和画面不同步
这两个问题是偶尔出现的,猜测是苹果AVPlayer底层的原因导致,有人提出解决方案是监控音频进度,图像线程跟音频保持一致,不同步的时候等待。但是如何实现还在研究中,如果有哪位牛人解决过类似问题,还多多请指教。
0 0
- AVPlayer实现简单播放器
- 基于AVPlayer简单封装的播放器
- CYC-AVPlayer播放器的简单使用
- AVPlayer简单地视频播放器
- iOS开发--AVPlayer实现音乐播放器
- AVPlayer 实现视频播放器的开发
- iOS开发--AVPlayer实现音乐播放器
- AVPlayer实现视频播放
- AVPlayer 多媒体播放器
- AVPlayer 多媒体播放器
- 基于AVPlayer播放器
- 视频播放器AVPlayer
- 视频播放器 AVPlayer
- AVPlayer实现视频播放功能
- 通过AVPlayer实现视频播放
- CYC-AVPlayer播放器的简单运用-02
- AVPlayer自定义视频播放器
- 视频播放器(AVPlayer)
- Redis与Reactor模式
- Axis2发布WebService服务
- Server Tomcat v7.0 Server at localhost failed to start.
- git/github/MyEclipse结合使用
- 嵌入式开发
- AVPlayer实现简单播放器
- 会计原理--总论
- 社会要反思:高三学生学校坠亡 当天曾致电父亲称压力大
- C#整数三种强制类型转换int、Convert.ToInt32()、int.Parse()的区别
- Java的this与super关键字
- iOS开发小牛的第一篇日志
- php数组遍历
- WordCount背后的数据流
- 浅谈QFile