iOS,위 챗 모멘트 영상 캡 처 기능 구현
위 챗 은 현재 이렇게 보급 되 고 기능 도 점점 강해 지고 있 습 니 다.여러분 들 은 위 챗 친구 들 이 동 영상 을 캡 처 하 는 기능 이나 애플 이 동 영상 을 찍 는 것 이 동 영상 편집 기능 에 대해 잘 알 고 있 는 지 모 르 겠 습 니 다.(작가 님 은 여기 서도 위 챗 의 이 기능 도 애플 을 모방 한 것 이 라 고 추측 합 니 다)이 기능 은 확실히 편리 하고 실 용적 인 것 같 습 니 다.최근 에 작가 도 음성 영상 기능 을 연구 하고 있 기 때문에 이 기능 을 실현 하 였 습 니 다.
기능 은 사실 보기 에는 매우 간단 하고 실현 과정 도 적지 않 은 구 덩이 를 밟 았 다.한편 으로 는 기록 하기;다른 한편 으로 는 실현 과정 에 대한 재 정리 라 고 할 수 있 습 니 다.그러면 코드 를 보 셔 도 잘 알 수 있 습 니 다.
효과.
일단 제 가 이 룬 효 과 를 보도 록 하 겠 습 니 다.
이루어지다
실현 과정 분석
전체 기능 은 세 부분 으로 나 눌 수 있다.
비디오 재생
이 부분 은 저희 가 영상 재생 기 를 따로 봉 하면 돼 요.
아래 의 미끄럼 보기이 부분 은 실현 과정 이 비교적 복잡 해서 모두 네 부분 으로 나 뉘 었 다.회색 커버,좌우 손잡이 슬라이더,슬라이더 중간 상하 두 줄,이미지 관리 보기
컨트롤 러 보기 논리 조립 및 기능 구현
비디오 플레이어 의 패키지
여기 서 AVPlayer,playerLayer,AVPlayerItem 등 세 가지 유형 을 사용 하여 영상 재생 기능 을 실현 했다.전체 사건 이 KVO 감청 에 기반 한 것 이기 때문에 Block 코드 를 추가 해 대외 감청 사용 을 제공 했다.
#import "FOFMoviePlayer.h"
@interface FOFMoviePlayer()
{
AVPlayerLooper *_playerLooper;
AVPlayerItem *_playItem;
BOOL _loop;
}
@property(nonatomic,strong)NSURL *url;
@property(nonatomic,strong)AVPlayer *player;
@property(nonatomic,strong)AVPlayerLayer *playerLayer;
@property(nonatomic,strong)AVPlayerItem *playItem;
@property (nonatomic,assign) CMTime duration;
@end
@implementation FOFMoviePlayer
-(instancetype)initWithFrame:(CGRect)frame url:(NSURL *)url superLayer:(CALayer *)superLayer{
self = [super init];
if (self) {
[self initplayers:superLayer];
_playerLayer.frame = frame;
self.url = url;
}
return self;
}
-(instancetype)initWithFrame:(CGRect)frame url:(NSURL *)url superLayer:(CALayer *)superLayer loop:(BOOL)loop{
self = [self initWithFrame:frame url:url superLayer:superLayer];
if (self) {
_loop = loop;
}
return self;
}
- (void)initplayers:(CALayer *)superLayer{
self.player = [[AVPlayer alloc] init];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.videoGravity = AVLayerVideoGravityResize;
[superLayer addSublayer:self.playerLayer];
}
- (void)initLoopPlayers:(CALayer *)superLayer{
self.player = [[AVQueuePlayer alloc] init];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.videoGravity = AVLayerVideoGravityResize;
[superLayer addSublayer:self.playerLayer];
}
-(void)fof_play{
[self.player play];
}
-(void)fof_pause{
[self.player pause];
}
#pragma mark - Observe
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItem *item = (AVPlayerItem *)object;
AVPlayerItemStatus status = [[change objectForKey:@"new"] intValue]; //
if (status == AVPlayerItemStatusReadyToPlay) {
_duration = item.duration;// , AVPlayerItem
NSLog(@" ");
if (self.blockStatusReadyPlay) {
self.blockStatusReadyPlay(item);
}
} else if (status == AVPlayerItemStatusFailed) {
if (self.blockStatusFailed) {
self.blockStatusFailed();
}
AVPlayerItem *item = (AVPlayerItem *)object;
NSLog(@"%@",item.error);
NSLog(@"AVPlayerStatusFailed");
} else {
self.blockStatusUnknown();
NSLog(@"%@",item.error);
NSLog(@"AVPlayerStatusUnknown");
}
}else if ([keyPath isEqualToString:@"tracking"]){
NSInteger status = [change[@"new"] integerValue];
if (self.blockTracking) {
self.blockTracking(status);
}
if (status) {//
[self.player pause];
}else{//
}
}else if ([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray *array = _playItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//
CGFloat startSeconds = CMTimeGetSeconds(timeRange.start);
CGFloat durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;//
double progress = totalBuffer/CMTimeGetSeconds(_duration);
if (self.blockLoadedTimeRanges) {
self.blockLoadedTimeRanges(progress);
}
NSLog(@" :%f",totalBuffer);
}else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
NSLog(@" , !");
}else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]){
if (self.blockPlaybackLikelyToKeepUp) {
self.blockPlaybackLikelyToKeepUp([change[@"new"] boolValue]);
}
}
}
-(void)setUrl:(NSURL *)url{
_url = url;
[self.player replaceCurrentItemWithPlayerItem:self.playItem];
}
-(AVPlayerItem *)playItem{
_playItem = [[AVPlayerItem alloc] initWithURL:_url];
// , 、 、
[_playItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//
[_playItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
// , :
[_playItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
//
[_playItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(private_playerMovieFinish) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
return _playItem;
}
- (void)private_playerMovieFinish{
NSLog(@" ");
if (self.blockPlayToEndTime) {
self.blockPlayToEndTime();
}
if (_loop) {//
[self.player pause];
CMTime time = CMTimeMake(1, 1);
__weak typeof(self)this = self;
[self.player seekToTime:time completionHandler:^(BOOL finished) {
[this.player play];
}];
}
}
-(void)dealloc{
NSLog(@"----- -----");
}
@end
동 영상 재생 기 는 중요 하지 않 습 니 다.저 자 는 동 영상 재생 기 에 관 한 것 을 단독으로 쓸 계획 입 니 다.아래 미끄럼 보기
회색 가리개
회색 커버 는 쉬 워 요.여 기 는 작가 님 이 UIView 만 쓰 셨 어 요.
self.leftMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, height)];
self.leftMaskView.backgroundColor = [UIColor grayColor];
self.leftMaskView.alpha = 0.8;
[self addSubview:self.leftMaskView];
self.rightMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, height)];
self.rightMaskView.backgroundColor = [UIColor grayColor];
self.rightMaskView.alpha = 0.8;
슬라이더 중간 상하 두 줄이 두 선 은 하나의 보기 라인 을 단독으로 봉 하여 처음에는 하나의 UIView 를 사용 하면 좋 겠 다 고 생각 했 지만 문 제 는 손잡이 의 미끄럼 이 선의 미끄럼 속도 와 일치 하지 않 고 선 이 비교적 느리다 는 것 이다.
@implementation Line
-(void)setBeginPoint:(CGPoint)beginPoint{
_beginPoint = beginPoint;
[self setNeedsDisplay];
}
-(void)setEndPoint:(CGPoint)endPoint{
_endPoint = endPoint;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 3);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0.9 alpha:1].CGColor);
CGContextMoveToPoint(context, self.beginPoint.x, self.beginPoint.y);
CGContextAddLineToPoint(context, self.endPoint.x, self.endPoint.y);
CGContextStrokePath(context);
}
그림 관리 보기손잡이,선,가 려 진 논 리 를 조립 하고 그림 을 표시 하 는 데 사용 되 는 VideoPieces 를 봉 했다.그림 이 10 장 밖 에 없 기 때문에 여 기 는 하나의 for 순환 으로 10 개의 UIImageView 가 추가 되 었 습 니 다.
@interface VideoPieces()
{
CGPoint _beginPoint;
}
@property(nonatomic,strong) Haft *leftHaft;
@property(nonatomic,strong) Haft *rightHaft;
@property(nonatomic,strong) Line *topLine;
@property(nonatomic,strong) Line *bottomLine;
@property(nonatomic,strong) UIView *leftMaskView;
@property(nonatomic,strong) UIView *rightMaskView;
@end
@implementation VideoPieces
-(instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
[self initSubViews:frame];
}
return self;
}
- (void)initSubViews:(CGRect)frame{
CGFloat height = CGRectGetHeight(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat minGap = 30;
CGFloat widthHaft = 10;
CGFloat heightLine = 3;
_leftHaft = [[Haft alloc] initWithFrame:CGRectMake(0, 0, widthHaft, height)];
_leftHaft.alpha = 0.8;
_leftHaft.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
_leftHaft.rightEdgeInset = 20;
_leftHaft.lefEdgeInset = 5;
__weak typeof(self) this = self;
[_leftHaft setBlockMove:^(CGPoint point) {
CGFloat maxX = this.rightHaft.frame.origin.x-minGap;
if (point.x=minX) {
this.topLine.endPoint = CGPointMake(point.x-widthHaft, heightLine/2.0);
this.bottomLine.endPoint = CGPointMake(point.x-widthHaft, heightLine/2.0);
this.rightHaft.frame = CGRectMake(point.x, 0, widthHaft, height);
this.rightMaskView.frame = CGRectMake(point.x+widthHaft, 0, width-point.x-widthHaft, height);
if (this.blockSeekOffRight) {
this.blockSeekOffRight(point.x);
}
}
}];
[_rightHaft setBlockMoveEnd:^{
if (this.blockMoveEnd) {
this.blockMoveEnd();
}
}];
_topLine = [[Line alloc] init];
_topLine.alpha = 0.8;
_topLine.frame = CGRectMake(widthHaft, 0, width-2*widthHaft, heightLine);
_topLine.beginPoint = CGPointMake(0, heightLine/2.0);
_topLine.endPoint = CGPointMake(CGRectGetWidth(_topLine.bounds), heightLine/2.0);
_topLine.backgroundColor = [UIColor clearColor];
[self addSubview:_topLine];
_bottomLine = [[Line alloc] init];
_bottomLine.alpha = 0.8;
_bottomLine.frame = CGRectMake(widthHaft, height-heightLine, width-2*widthHaft, heightLine);
_bottomLine.beginPoint = CGPointMake(0, heightLine/2.0);
_bottomLine.endPoint = CGPointMake(CGRectGetWidth(_bottomLine.bounds), heightLine/2.0);
_bottomLine.backgroundColor = [UIColor clearColor];
[self addSubview:_bottomLine];
[self addSubview:_leftHaft];
[self addSubview:_rightHaft];
self.leftMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, height)];
self.leftMaskView.backgroundColor = [UIColor grayColor];
self.leftMaskView.alpha = 0.8;
[self addSubview:self.leftMaskView];
self.rightMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, height)];
self.rightMaskView.backgroundColor = [UIColor grayColor];
self.rightMaskView.alpha = 0.8;
[self addSubview:self.rightMaskView];
}
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{
UITouch *touch = touches.anyObject;
_beginPoint = [touch locationInView:self];
}
손잡이 의 실현손잡이 의 실현 은 여기 서 최적화 되 었 다.바로 미 끄 러 질 때 비교적 민감 하 다 는 것 이다.처음에 손가락 으로 미 끄 러 질 때 매우 민감 하지 않 고 자주 손가락 이 미 끄 러 졌 지만 손 을 움 직 이지 않 았 다.
민감 도 를 높이 는 방법 은 수신 이벤트 영역의 크기 를 늘 리 고 다시 쓰 는 것 입 니 다.-(BOOL)point Inside:(CGPoint)point with Event:(UIEvent*)event 이 방법 입 니 다.
@implementation Haft
-(instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = true;
}
return self;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
CGRect rect = CGRectMake(self.bounds.origin.x-self.lefEdgeInset, self.bounds.origin.y-self.topEdgeInset, CGRectGetWidth(self.bounds)+self.lefEdgeInset+self.rightEdgeInset, CGRectGetHeight(self.bounds)+self.bottomEdgeInset+self.topEdgeInset);
if (CGRectContainsPoint(rect, point)) {
return YES;
}
return NO;
}
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{
NSLog(@" ");
}
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event{
NSLog(@"Move");
UITouch *touch = touches.anyObject;
CGPoint point = [touch locationInView:self.superview];
CGFloat maxX = CGRectGetWidth(self.superview.bounds)-CGRectGetWidth(self.bounds);
if (point.x>maxX) {
point.x = maxX;
}
if (point.x>=0&&point.x<=(CGRectGetWidth(self.superview.bounds)-CGRectGetWidth(self.bounds))&&self.blockMove) {
self.blockMove(point);
}
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event{
if (self.blockMoveEnd) {
self.blockMoveEnd();
}
}
- (void)drawRect:(CGRect)rect {
CGFloat width = CGRectGetWidth(self.bounds);
CGFloat height = CGRectGetHeight(self.bounds);
CGFloat lineWidth = 1.5;
CGFloat lineHeight = 12;
CGFloat gap = (width-lineWidth*2)/3.0;
CGFloat lineY = (height-lineHeight)/2.0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [[UIColor grayColor] colorWithAlphaComponent:0.8].CGColor);
CGContextMoveToPoint(context, gap+lineWidth/2, lineY);
CGContextAddLineToPoint(context, gap+lineWidth/2, lineY+lineHeight);
CGContextStrokePath(context);
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [[UIColor grayColor] colorWithAlphaComponent:0.8].CGColor);
CGContextMoveToPoint(context, gap*2+lineWidth+lineWidth/2, lineY);
CGContextAddLineToPoint(context, gap*2+lineWidth+lineWidth/2, lineY+lineHeight);
CGContextStrokePath(context);
}
컨트롤 러 보기 논리 조립 및 기능 구현이 부분의 논 리 는 가장 중요 하고 복잡 하 다.
미리 보기 그림 10 장 가 져 오기
- (NSArray *)getVideoThumbnail:(NSString *)path count:(NSInteger)count splitCompleteBlock:(void(^)(BOOL success, NSMutableArray *splitimgs))splitCompleteBlock {
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]];
NSMutableArray *arrayImages = [NSMutableArray array];
[asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
// generator.maximumSize = CGSizeMake(480,136);// CGSizeMake(480,136), {240, 136}。
generator.appliesPreferredTrackTransform = YES;// 。 。
/** , 。 , **/
generator.requestedTimeToleranceAfter = kCMTimeZero;
generator.requestedTimeToleranceBefore = kCMTimeZero;
Float64 seconds = CMTimeGetSeconds(asset.duration);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i
CMTime time = CMTimeMakeWithSeconds(i*(seconds/10.0),1);//
[array addObject:[NSValue valueWithCMTime:time]];
}
__block int i = 0;
[generator generateCGImagesAsynchronouslyForTimes:array completionHandler:^(CMTime requestedTime, CGImageRef _Nullable imageRef, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
i++;
if (result==AVAssetImageGeneratorSucceeded) {
UIImage *image = [UIImage imageWithCGImage:imageRef];
[arrayImages addObject:image];
}else{
NSLog(@" !!!");
}
if (i==count) {
dispatch_async(dispatch_get_main_queue(), ^{
splitCompleteBlock(YES,arrayImages);
});
}
}];
}];
return arrayImages;
}
10 장의 그림 은 쉽게 얻 을 수 있 지만 여기 서 주의해 야 할 것 은 리 셋 할 때 비동기 홈 팀 에 리 셋 해 야 합 니 다!그림 표시 지연 이 심 하지 않 으 면좌우 슬라이더 사건 감청
[_videoPieces setBlockSeekOffLeft:^(CGFloat offX) {
this.seeking = true;
[this.moviePlayer fof_pause];
this.lastStartSeconds = this.totalSeconds*offX/CGRectGetWidth(this.videoPieces.bounds);
[this.moviePlayer.player seekToTime:CMTimeMakeWithSeconds(this.lastStartSeconds, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}];
[_videoPieces setBlockSeekOffRight:^(CGFloat offX) {
this.seeking = true;
[this.moviePlayer fof_pause];
this.lastEndSeconds = this.totalSeconds*offX/CGRectGetWidth(this.videoPieces.bounds);
[this.moviePlayer.player seekToTime:CMTimeMakeWithSeconds(this.lastEndSeconds, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}];
좌우 슬라이더 의 이 벤트 를 감청 하여 오프셋 거 리 를 시간 으로 전환 하여 플레이어 의 시작 시간 과 종료 시간 을 설정 합 니 다.반복 재생
self.timeObserverToken = [self.moviePlayer.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
if (!this.seeking) {
if (fabs(CMTimeGetSeconds(time)-this.lastEndSeconds)<=0.02) {
[this.moviePlayer fof_pause];
[this private_replayAtBeginTime:this.lastStartSeconds];
}
}
}];
여기에 두 가지 주의 점 이 있다.1.addPeriodicTimeObserver ForInterval 을 방출 해 야 합 니 다.그렇지 않 으 면 메모리 가 누 출 될 수 있 습 니 다.
-(void)dealloc{
[self.moviePlayer.player removeTimeObserver:self.timeObserverToken];
}
2.여기 서 재생 시간 을 감청 한 다음 에 우리 가 오른쪽 손 을 끌 어 당 기 는 시간 에 이 르 렀 는 지 계산 하고 이 르 면 다시 재생 합 니 다.이 문 제 는 작가 가 오랫동안 생각 했 는데,어떻게 하면 재생 하면 서 캡 처 할 수 있 습 니까?하마터면 잘못된 곳 에 들 어 갈 뻔 했 는데,정말 동 영상 을 캡 처 하 러 갔다.사실 여 기 는 동 영상 을 캡 처 하지 않 고 재생 시간 과 종료 시간 만 조절 하면 되 고 마지막 으로 한 번 만 캡 처 하면 된다.총결산
이번 위 챗 영상 편집 이 이 뤄 지 는 과정 에서 작은 문제 가 많 았 던 것 은 사실이다.하지만 꼼꼼 한 연 구 를 통 해 완벽 하 게 이 뤄 져 무 거 운 짐 을 벗 은 듯 한 느낌 이 든다.하하.
소스 코드
GitHub 소스 코드
총결산
위 에서 말 한 것 은 편집장 님 께 서 소개 해 주신 iOS 가 위 챗 모멘트 영상 캡 처 기능 을 실현 하 는 것 입 니 다.여러분 께 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.편집장 님 께 서 바로 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Swift의 패스트 패스Objective-C를 대체하기 위해 만들어졌지만 Xcode는 Objective-C 런타임 라이브러리를 사용하기 때문에 Swift와 함께 C, C++ 및 Objective-C를 컴파일할 수 있습니다. Xcode는 S...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.