KCSP/ProductApp/Pods/ICGVideoTrimmer/Source/ICGVideoTrimmerView.m

384 lines
15 KiB
Objective-C

//
// ICGVideoTrimmerView.m
// ICGVideoTrimmer
//
// Created by Huong Do on 1/18/15.
// Copyright (c) 2015 ichigo. All rights reserved.
//
#import "ICGVideoTrimmerView.h"
#import "ICGThumbView.h"
#import "ICGRulerView.h"
@interface ICGVideoTrimmerView() <UIScrollViewDelegate>
@property (strong, nonatomic) UIView *contentView;
@property (strong, nonatomic) UIView *frameView;
@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) AVAssetImageGenerator *imageGenerator;
@property (strong, nonatomic) UIView *leftOverlayView;
@property (strong, nonatomic) UIView *rightOverlayView;
@property (strong, nonatomic) ICGThumbView *leftThumbView;
@property (strong, nonatomic) ICGThumbView *rightThumbView;
@property (strong, nonatomic) UIView *trackerView;
@property (strong, nonatomic) UIView *topBorder;
@property (strong, nonatomic) UIView *bottomBorder;
@property (nonatomic) CGFloat startTime;
@property (nonatomic) CGFloat endTime;
@property (nonatomic) CGFloat widthPerSecond;
@property (nonatomic) CGPoint leftStartPoint;
@property (nonatomic) CGPoint rightStartPoint;
@property (nonatomic) CGFloat overlayWidth;
@end
@implementation ICGVideoTrimmerView
#pragma mark - Initiation
- (instancetype)initWithAsset:(AVAsset *)asset
{
return [self initWithFrame:CGRectZero asset:asset];
}
- (instancetype)initWithFrame:(CGRect)frame asset:(AVAsset *)asset
{
self = [super initWithFrame:frame];
if (self) {
_asset = asset;
[self resetSubviews];
}
return self;
}
#pragma mark - Private methods
- (CGFloat)thumbWidth
{
return _thumbWidth ?: 10;
}
- (CGFloat)maxLength
{
return _maxLength ?: 15;
}
- (CGFloat)minLength
{
return _minLength ?: 3;
}
- (void)resetSubviews
{
[self setBackgroundColor:[UIColor blackColor]];
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))];
[self addSubview:self.scrollView];
[self.scrollView setDelegate:self];
[self.scrollView setShowsHorizontalScrollIndicator:NO];
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame))];
[self.scrollView setContentSize:self.contentView.frame.size];
[self.scrollView addSubview:self.contentView];
CGFloat ratio = self.showsRulerView ? 0.7 : 1.0;
self.frameView = [[UIView alloc] initWithFrame:CGRectMake(self.thumbWidth, 0, CGRectGetWidth(self.contentView.frame)-2*self.thumbWidth, CGRectGetHeight(self.contentView.frame)*ratio)];
[self.frameView.layer setMasksToBounds:YES];
[self.contentView addSubview:self.frameView];
[self addFrames];
if (self.showsRulerView) {
CGRect rulerFrame = CGRectMake(0, CGRectGetHeight(self.contentView.frame)*0.7, CGRectGetWidth(self.contentView.frame)+self.thumbWidth, CGRectGetHeight(self.contentView.frame)*0.3);
ICGRulerView *rulerView = [[ICGRulerView alloc] initWithFrame:rulerFrame widthPerSecond:self.widthPerSecond themeColor:self.themeColor];
[self.contentView addSubview:rulerView];
}
// add borders
self.topBorder = [[UIView alloc] init];
[self.topBorder setBackgroundColor:self.themeColor];
[self addSubview:self.topBorder];
self.bottomBorder = [[UIView alloc] init];
[self.bottomBorder setBackgroundColor:self.themeColor];
[self addSubview:self.bottomBorder];
// width for left and right overlay views
self.overlayWidth = CGRectGetWidth(self.frame) - (self.minLength * self.widthPerSecond);
// add left overlay view
self.leftOverlayView = [[UIView alloc] initWithFrame:CGRectMake(self.thumbWidth - self.overlayWidth, 0, self.overlayWidth, CGRectGetHeight(self.frameView.frame))];
CGRect leftThumbFrame = CGRectMake(self.overlayWidth-self.thumbWidth, 0, self.thumbWidth, CGRectGetHeight(self.frameView.frame));
if (self.leftThumbImage) {
self.leftThumbView = [[ICGThumbView alloc] initWithFrame:leftThumbFrame thumbImage:self.leftThumbImage];
} else {
self.leftThumbView = [[ICGThumbView alloc] initWithFrame:leftThumbFrame color:self.themeColor right:NO];
}
self.trackerView = [[UIView alloc] initWithFrame:CGRectMake(self.thumbWidth, -5, 3, CGRectGetHeight(self.frameView.frame) + 10)];
self.trackerView.backgroundColor = self.trackerColor ? self.trackerColor : [UIColor whiteColor];
self.trackerView.layer.masksToBounds = true;
self.trackerView.layer.cornerRadius = 2;
[self addSubview:self.trackerView];
[self.leftThumbView.layer setMasksToBounds:YES];
[self.leftOverlayView addSubview:self.leftThumbView];
[self.leftOverlayView setUserInteractionEnabled:YES];
UIPanGestureRecognizer *leftPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLeftOverlayView:)];
[self.leftOverlayView addGestureRecognizer:leftPanGestureRecognizer];
[self.leftOverlayView setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.8]];
[self addSubview:self.leftOverlayView];
// add right overlay view
CGFloat rightViewFrameX = CGRectGetWidth(self.frameView.frame) < CGRectGetWidth(self.frame) ? CGRectGetMaxX(self.frameView.frame) : CGRectGetWidth(self.frame) - self.thumbWidth;
self.rightOverlayView = [[UIView alloc] initWithFrame:CGRectMake(rightViewFrameX, 0, self.overlayWidth, CGRectGetHeight(self.frameView.frame))];
if (self.rightThumbImage) {
self.rightThumbView = [[ICGThumbView alloc] initWithFrame:CGRectMake(0, 0, self.thumbWidth, CGRectGetHeight(self.frameView.frame)) thumbImage:self.rightThumbImage];
} else {
self.rightThumbView = [[ICGThumbView alloc] initWithFrame:CGRectMake(0, 0, self.thumbWidth, CGRectGetHeight(self.frameView.frame)) color:self.themeColor right:YES];
}
[self.rightThumbView.layer setMasksToBounds:YES];
[self.rightOverlayView addSubview:self.rightThumbView];
[self.rightOverlayView setUserInteractionEnabled:YES];
UIPanGestureRecognizer *rightPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveRightOverlayView:)];
[self.rightOverlayView addGestureRecognizer:rightPanGestureRecognizer];
[self.rightOverlayView setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.8]];
[self addSubview:self.rightOverlayView];
[self updateBorderFrames];
[self notifyDelegate];
}
- (void)updateBorderFrames
{
CGFloat height = self.borderWidth ? self.borderWidth : 1;
[self.topBorder setFrame:CGRectMake(CGRectGetMaxX(self.leftOverlayView.frame), 0, CGRectGetMinX(self.rightOverlayView.frame)-CGRectGetMaxX(self.leftOverlayView.frame), height)];
[self.bottomBorder setFrame:CGRectMake(CGRectGetMaxX(self.leftOverlayView.frame), CGRectGetHeight(self.frameView.frame)-height, CGRectGetMinX(self.rightOverlayView.frame)-CGRectGetMaxX(self.leftOverlayView.frame), height)];
}
- (void)moveLeftOverlayView:(UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
self.leftStartPoint = [gesture locationInView:self];
break;
case UIGestureRecognizerStateChanged:
{
CGPoint point = [gesture locationInView:self];
int deltaX = point.x - self.leftStartPoint.x;
CGPoint center = self.leftOverlayView.center;
CGFloat newLeftViewMidX = center.x += deltaX;;
CGFloat maxWidth = CGRectGetMinX(self.rightOverlayView.frame) - (self.minLength * self.widthPerSecond);
CGFloat newLeftViewMinX = newLeftViewMidX - self.overlayWidth/2;
if (newLeftViewMinX < self.thumbWidth - self.overlayWidth) {
newLeftViewMidX = self.thumbWidth - self.overlayWidth + self.overlayWidth/2;
} else if (newLeftViewMinX + self.overlayWidth > maxWidth) {
newLeftViewMidX = maxWidth - self.overlayWidth / 2;
}
self.leftOverlayView.center = CGPointMake(newLeftViewMidX, self.leftOverlayView.center.y);
self.leftStartPoint = point;
[self updateBorderFrames];
[self notifyDelegate];
break;
}
default:
break;
}
}
- (void)moveRightOverlayView:(UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
self.rightStartPoint = [gesture locationInView:self];
break;
case UIGestureRecognizerStateChanged:
{
CGPoint point = [gesture locationInView:self];
int deltaX = point.x - self.rightStartPoint.x;
CGPoint center = self.rightOverlayView.center;
CGFloat newRightViewMidX = center.x += deltaX;
CGFloat minX = CGRectGetMaxX(self.leftOverlayView.frame) + self.minLength * self.widthPerSecond;
CGFloat maxX = CMTimeGetSeconds([self.asset duration]) <= self.maxLength + 0.5 ? CGRectGetMaxX(self.frameView.frame) : CGRectGetWidth(self.frame) - self.thumbWidth;
if (newRightViewMidX - self.overlayWidth/2 < minX) {
newRightViewMidX = minX + self.overlayWidth/2;
} else if (newRightViewMidX - self.overlayWidth/2 > maxX) {
newRightViewMidX = maxX + self.overlayWidth/2;
}
self.rightOverlayView.center = CGPointMake(newRightViewMidX, self.rightOverlayView.center.y);
self.rightStartPoint = point;
[self updateBorderFrames];
[self notifyDelegate];
break;
}
default:
break;
}
}
- (void)seekToTime:(CGFloat) time
{
CGFloat posToMove = time * self.widthPerSecond + self.thumbWidth - self.scrollView.contentOffset.x;
CGRect trackerFrame = self.trackerView.frame;
trackerFrame.origin.x = posToMove;
self.trackerView.frame = trackerFrame;
}
- (void)hideTracker:(BOOL)flag
{
self.trackerView.hidden = flag;
}
- (void)notifyDelegate
{
CGFloat start = CGRectGetMaxX(self.leftOverlayView.frame) / self.widthPerSecond + (self.scrollView.contentOffset.x -self.thumbWidth) / self.widthPerSecond;
if (!self.trackerView.hidden && start != self.startTime) {
[self seekToTime:start];
}
self.startTime = start;
self.endTime = CGRectGetMinX(self.rightOverlayView.frame) / self.widthPerSecond + (self.scrollView.contentOffset.x - self.thumbWidth) / self.widthPerSecond;
[self.delegate trimmerView:self didChangeLeftPosition:self.startTime rightPosition:self.endTime];
}
- (void)addFrames
{
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];
self.imageGenerator.appliesPreferredTrackTransform = YES;
if ([self isRetina]){
self.imageGenerator.maximumSize = CGSizeMake(CGRectGetWidth(self.frameView.frame)*2, CGRectGetHeight(self.frameView.frame)*2);
} else {
self.imageGenerator.maximumSize = CGSizeMake(CGRectGetWidth(self.frameView.frame), CGRectGetHeight(self.frameView.frame));
}
CGFloat picWidth = 0;
// First image
NSError *error;
CMTime actualTime;
CGImageRef halfWayImage = [self.imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:&actualTime error:&error];
UIImage *videoScreen;
if ([self isRetina]){
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage scale:2.0 orientation:UIImageOrientationUp];
} else {
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage];
}
if (halfWayImage != NULL) {
UIImageView *tmp = [[UIImageView alloc] initWithImage:videoScreen];
CGRect rect = tmp.frame;
rect.size.width = videoScreen.size.width;
tmp.frame = rect;
[self.frameView addSubview:tmp];
picWidth = tmp.frame.size.width;
CGImageRelease(halfWayImage);
}
Float64 duration = CMTimeGetSeconds([self.asset duration]);
CGFloat screenWidth = CGRectGetWidth(self.frame) - 2*self.thumbWidth; // quick fix to make up for the width of thumb views
NSInteger actualFramesNeeded;
CGFloat frameViewFrameWidth = (duration / self.maxLength) * screenWidth;
[self.frameView setFrame:CGRectMake(self.thumbWidth, 0, frameViewFrameWidth, CGRectGetHeight(self.frameView.frame))];
CGFloat contentViewFrameWidth = CMTimeGetSeconds([self.asset duration]) <= self.maxLength + 0.5 ? screenWidth + 30 : frameViewFrameWidth;
[self.contentView setFrame:CGRectMake(0, 0, contentViewFrameWidth, CGRectGetHeight(self.contentView.frame))];
[self.scrollView setContentSize:self.contentView.frame.size];
NSInteger minFramesNeeded = screenWidth / picWidth + 1;
actualFramesNeeded = (duration / self.maxLength) * minFramesNeeded + 1;
Float64 durationPerFrame = duration / (actualFramesNeeded*1.0);
self.widthPerSecond = frameViewFrameWidth / duration;
int preferredWidth = 0;
NSMutableArray *times = [[NSMutableArray alloc] init];
for (int i=1; i<actualFramesNeeded; i++){
CMTime time = CMTimeMakeWithSeconds(i*durationPerFrame, 600);
[times addObject:[NSValue valueWithCMTime:time]];
UIImageView *tmp = [[UIImageView alloc] initWithImage:videoScreen];
tmp.tag = i;
CGRect currentFrame = tmp.frame;
currentFrame.origin.x = i*picWidth;
currentFrame.size.width = picWidth;
preferredWidth += currentFrame.size.width;
if( i == actualFramesNeeded-1){
currentFrame.size.width-=6;
}
tmp.frame = currentFrame;
dispatch_async(dispatch_get_main_queue(), ^{
[self.frameView addSubview:tmp];
});
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=1; i<=[times count]; i++) {
CMTime time = [((NSValue *)[times objectAtIndex:i-1]) CMTimeValue];
CGImageRef halfWayImage = [self.imageGenerator copyCGImageAtTime:time actualTime:NULL error:NULL];
UIImage *videoScreen;
if ([self isRetina]){
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage scale:2.0 orientation:UIImageOrientationUp];
} else {
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage];
}
CGImageRelease(halfWayImage);
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = (UIImageView *)[self.frameView viewWithTag:i];
[imageView setImage:videoScreen];
});
}
});
}
- (BOOL)isRetina
{
return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
([UIScreen mainScreen].scale > 1.0));
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (CMTimeGetSeconds([self.asset duration]) <= self.maxLength + 0.5) {
[UIView animateWithDuration:0.3 animations:^{
[scrollView setContentOffset:CGPointZero];
}];
}
[self notifyDelegate];
}
@end