Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions VideoThumbnailSelectionView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 52;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -111,10 +111,11 @@
};
};
buildConfigurationList = E91E336E1D017AB2004EF96C /* Build configuration list for PBXProject "VideoThumbnailSelectionView" */;
compatibilityVersion = "Xcode 3.2";
compatibilityVersion = "Xcode 11.0";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = E91E336A1D017AB2004EF96C;
Expand Down Expand Up @@ -195,6 +196,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand Down Expand Up @@ -236,6 +238,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
Expand All @@ -254,7 +257,11 @@
INFOPLIST_FILE = VideoThumbnailSelectionView/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.sarpsolakoglu.VideoThumbnailSelectionView;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand All @@ -273,7 +280,11 @@
INFOPLIST_FILE = VideoThumbnailSelectionView/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.sarpsolakoglu.VideoThumbnailSelectionView;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand All @@ -299,6 +310,7 @@
E91E337E1D017AB2004EF96C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
Expand Down
7 changes: 4 additions & 3 deletions VideoThumbnailSelectionView/SelectionThumb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

import UIKit

class SelectionThumb: UIView {
final class SelectionThumb: UIView {
var previewImageView: UIImageView!

override init(frame: CGRect) {
previewImageView = UIImageView(frame: CGRectMake(2, 2, frame.size.width - 4, frame.size.height - 4))
previewImageView = UIImageView(frame: CGRect(x: 2, y: 2, width: frame.size.width - 4, height: frame.size.height - 4))
super.init(frame: frame)
backgroundColor = .whiteColor()

backgroundColor = .white
addSubview(previewImageView)
}

Expand Down
98 changes: 49 additions & 49 deletions VideoThumbnailSelectionView/VideoThumbnailSelectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit
import AVFoundation

@objc public class VideoThumbnailSelectionView: UIView {
@objc public final class VideoThumbnailSelectionView: UIView {

//MARK: - Private params

Expand All @@ -33,8 +33,8 @@ import AVFoundation
didSet {
if asset != nil {
generator = AVAssetImageGenerator(asset: asset!)
generator!.requestedTimeToleranceBefore = kCMTimeZero
generator!.requestedTimeToleranceAfter = kCMTimeZero
generator!.requestedTimeToleranceBefore = CMTime.zero
generator!.requestedTimeToleranceAfter = CMTime.zero
} else {
generator = nil
}
Expand All @@ -47,7 +47,9 @@ import AVFoundation
View calls this block when selection changes. Should be set before loading video.
*/
public var onUpdatedImage: (UIImage)->() = { _ in }


/// This one tells the time at which the thumbnail is for
public var imageTime: Float64 = 0

/**
The opacity of the black shade color on the thumbnails. Defaults to 0.5.
Expand Down Expand Up @@ -75,7 +77,7 @@ import AVFoundation
/**
The color of the corner of the view.
*/
public var cornerColor: UIColor = .whiteColor() {
public var cornerColor: UIColor = .white {
didSet {
view.backgroundColor = cornerColor
}
Expand All @@ -84,7 +86,7 @@ import AVFoundation
/**
The corner thickness.
*/
public var cornerInsets: UIEdgeInsets = UIEdgeInsetsMake(8.0, 8.0, 8.0, 8.0) {
public var cornerInsets: UIEdgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) {
didSet {
topMargin.constant = cornerInsets.top
leftMargin.constant = cornerInsets.left
Expand Down Expand Up @@ -112,23 +114,20 @@ import AVFoundation
- completion: UIImage is returned in this block if succesful.
- failure: This block is called in case of failure.
*/
@objc public func snapshot(forSecond second: Float64, completion:(UIImage)->(), failure:(()->())?) {
@objc public func snapshot(forSecond second: Float64, completion: @escaping (UIImage)->(), failure:(()->())?) {
guard let generator = generator else {
if failure != nil {
failure!()
}
failure?()
return
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
if let image = self.generateThumbnail(generator: generator, second: second) {
dispatch_async(dispatch_get_main_queue(), {

DispatchQueue.global().async {
if let image = self.generateThumbnail(generator: generator, second: second) {
DispatchQueue.main.async {
completion(image)
})
}
return
} else {
if failure != nil {
failure!()
}
failure?()
return
}
}
Expand All @@ -147,27 +146,26 @@ import AVFoundation
if videoLoaded { return }
if videoLoading { return }

guard let assetVideoTrack : AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] else { return }
guard let assetVideoTrack : AVAssetTrack = asset.tracks(withMediaType: AVMediaType.video)[0] else { return }

self.asset = asset

activityIndicator.startAnimating()

dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
let size = assetVideoTrack.naturalSize
let thumbnailHeight = self.thumbnailView.bounds.size.height
let videoAspect = size.width / size.height
let thumbnailWidth = thumbnailHeight * videoAspect

let generator = AVAssetImageGenerator(asset: asset)
generator.requestedTimeToleranceBefore = kCMTimeZero
generator.requestedTimeToleranceAfter = kCMTimeZero
generator.requestedTimeToleranceBefore = .zero
generator.requestedTimeToleranceAfter = .zero

let thumbnailCount = Int(ceil(self.thumbnailView.bounds.size.width / thumbnailWidth))
let videoDuration = CMTimeGetSeconds(asset.duration)
let sampleInterval = videoDuration / Float64(thumbnailCount)

self.selectionThumb = SelectionThumb(frame: CGRectMake(self.thumbnailView.frame.origin.x, self.thumbnailView.frame.origin.y, thumbnailWidth, thumbnailHeight))
self.selectionThumb = SelectionThumb(frame: CGRect(x: self.thumbnailView.frame.origin.x, y: self.thumbnailView.frame.origin.y, width: thumbnailWidth, height: thumbnailHeight))

self.scrollOptions = ScrollOptions(startPoint: self.leftMargin.constant + thumbnailWidth / 2, endPoint: self.view.bounds.size.width - thumbnailWidth / 2 - self.rightMargin.constant)

Expand All @@ -180,8 +178,8 @@ import AVFoundation
self.selectionThumb!.previewImageView.image = image
self.view.addSubview(self.selectionThumb!)
}
let imageView = UIImageView(frame: CGRectMake(currentX, 0.0, thumbnailWidth, thumbnailHeight))
imageView.contentMode = .ScaleAspectFill
let imageView = UIImageView(frame: CGRect(x: currentX, y: 0.0, width: thumbnailWidth, height: thumbnailHeight))
imageView.contentMode = .scaleAspectFill
imageView.image = image
self.thumbnailView.addSubview(imageView)
self.thumbnails.append(imageView)
Expand All @@ -191,7 +189,8 @@ import AVFoundation
self.activityIndicator.stopAnimating()
self.videoLoaded = true
self.videoLoading = false
self.didScrollToPercent(0, override: true)
self.didScrollToPercent(percent: 0, override: true)

}
}

Expand Down Expand Up @@ -228,61 +227,62 @@ import AVFoundation
snapshot(forSecond: videoDuration * Float64(percent), completion: {[weak self] image in
self?.selectionThumb?.previewImageView.image = image
self?.onUpdatedImage(image)
self?.imageTime = videoDuration * Float64(percent)
self?.shouldUpdateFrame = true
}, failure: {[weak self] in
self?.shouldUpdateFrame = true
})
}

private func generateThumbnail(generator generator: AVAssetImageGenerator, second: Float64) -> UIImage? {
let time = CMTimeMake(Int64(second * 60), 60)
func generateThumbnail(generator: AVAssetImageGenerator, second: Float64) -> UIImage? {
let time = CMTimeMake(value: Int64(second * 60), timescale: 60)
do {
let imgRef = try generator.copyCGImageAtTime(time, actualTime: nil)
return UIImage(CGImage: imgRef)
let imgRef = try generator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: imgRef)
} catch {
return nil
}
}

//MARK: - Private loader functions
private func setup(frame frame: CGRect?) {
//MARK: - Private lounctions
private func setup(frame: CGRect?) {
loadNib()
if frame != nil {
view.frame = CGRectMake(0, 0, CGRectGetWidth(frame!), CGRectGetHeight(frame!))
view.frame = CGRect(x: 0, y: 0, width: frame!.width, height: frame!.height)
}
addSubview(view)
pinView()
thumbnailView.layer.masksToBounds = true
}

private func loadNib() {
NSBundle(forClass: VideoThumbnailSelectionView.self).loadNibNamed("VideoThumbnailSelectionView", owner: self, options: nil)
Bundle(for: VideoThumbnailSelectionView.self).loadNibNamed("VideoThumbnailSelectionView", owner: self, options: nil)
}

private func pinView() {
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: self , attribute: .Leading, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self , attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: view!, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
setNeedsLayout()
layoutIfNeeded()
}

//MARK: - Touches
override public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if scrollOptions == nil { return }
guard let selectionThumb = selectionThumb else { return }
if let touch = touches.first {
let loc = touch.locationInView(thumbnailView)
if CGRectContainsPoint(selectionThumb.frame, loc) {
let loc = touch.location(in: thumbnailView)
if selectionThumb.frame.contains(loc) {
scrollOptions!.thumbStartLocation = selectionThumb.center.x
scrollOptions!.scrollStartLocation = loc.x
scrollOptions!.currentlyScrolling = true

if zoomAnimationScale > 1.0 {
selectionThumb.layer.removeAllAnimations()
UIView.animateWithDuration(0.2, animations: {
UIView.animate(withDuration: 0.2, animations: {
selectionThumb.layer.transform = CATransform3DScale(CATransform3DIdentity, self.zoomAnimationScale, self.zoomAnimationScale, 1.0)
})
}
Expand All @@ -291,27 +291,27 @@ import AVFoundation

}

override public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if scrollOptions == nil { return }
if !(scrollOptions!.currentlyScrolling) { return }
guard let selectionThumb = selectionThumb else { return }
if let touch = touches.first {
let loc = touch.locationInView(thumbnailView)
let newX = scrollOptions!.getNewLocationAccordingToPoint(loc.x)
let loc = touch.location(in: thumbnailView)
let newX = scrollOptions!.getNewLocationAccordingToPoint(x: loc.x)
selectionThumb.center.x = newX
//call view
didScrollToPercent(scrollOptions!.scrollPercent, override: false)
didScrollToPercent(percent: scrollOptions!.scrollPercent, override: false)
}
}

override public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if scrollOptions == nil { return }
if !(scrollOptions!.currentlyScrolling) { return }
guard let selectionThumb = selectionThumb else { return }
didScrollToPercent(scrollOptions!.scrollPercent, override: true)
didScrollToPercent(percent: scrollOptions!.scrollPercent, override: true)
if zoomAnimationScale > 1.0 {
selectionThumb.layer.removeAllAnimations()
UIView.animateWithDuration(0.2, animations: {
UIView.animate(withDuration: 0.2, animations: {
selectionThumb.layer.transform = CATransform3DIdentity
})
}
Expand Down