Skip to content

Commit 6189730

Browse files
author
Danil Gontovnik
committed
Updated project structure
1 parent 3db74e1 commit 6189730

File tree

8 files changed

+252
-470
lines changed

8 files changed

+252
-470
lines changed

VideoViewController/TimelineView.swift

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,142 @@
1+
// TimelineView.swift
12
//
2-
// TimelineView.swift
3-
// VideoViewControllerExample
3+
// Copyright (c) 2016 Danil Gontovnik (http://gontovnik.com/)
44
//
5-
// Created by Danil Gontovnik on 1/4/16.
6-
// Copyright © 2016 Danil Gontovnik. All rights reserved.
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
711
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
822

923
import UIKit
1024

11-
class TimelineView: UIView {
25+
public class TimelineView: UIView {
1226

1327
// MARK: - Vars
1428

15-
var duration: Double = 0.0 {
29+
/// The duration of the video in seconds.
30+
public var duration: NSTimeInterval = 0.0 {
1631
didSet { setNeedsDisplay() }
1732
}
1833

19-
var initialTime: Double = 0.0 {
34+
/// Time in seconds when rewind began.
35+
public var initialTime: NSTimeInterval = 0.0 {
2036
didSet {
2137
currentTime = initialTime
2238
}
2339
}
2440

25-
var currentTime: Double = 0.0 {
41+
/// Current timeline time in seconds.
42+
public var currentTime: NSTimeInterval = 0.0 {
2643
didSet {
2744
setNeedsDisplay()
2845
currentTimeDidChange?(currentTime)
2946
}
3047
}
3148

49+
/// Internal zoom variable.
3250
private var _zoom: CGFloat = 1.0 {
3351
didSet { setNeedsDisplay() }
3452
}
3553

36-
var zoom: CGFloat {
54+
/// The zoom of the timeline view. The higher zoom value, the more accurate rewind is. Default is 1.0.
55+
public var zoom: CGFloat {
3756
get { return _zoom }
3857
set { _zoom = max(min(newValue, maxZoom), minZoom) }
3958
}
4059

41-
var minZoom: CGFloat = 1.0 {
60+
/// Indicates minimum zoom value. Default is 1.0.
61+
public var minZoom: CGFloat = 1.0 {
4262
didSet { zoom = _zoom }
4363
}
4464

45-
var maxZoom: CGFloat = 3.5 {
65+
/// Indicates maximum zoom value. Default is 3.5.
66+
public var maxZoom: CGFloat = 3.5 {
4667
didSet { zoom = _zoom }
4768
}
4869

49-
var intervalWidth: CGFloat = 24.0 {
70+
/// The width of a line representing a specific time interval on a timeline. If zoom is not equal 1, then actual interval width equals to intervalWidth * zoom. Value will be used during rewind for calculations — for example, if zoom is 1, intervalWidth is 30 and intervalDuration is 15, then when user moves 10pixels left or right we will rewind by +5 or -5 seconds;
71+
public var intervalWidth: CGFloat = 24.0 {
5072
didSet { setNeedsDisplay() }
5173
}
5274

53-
var intervalDuration: CGFloat = 15.0 {
75+
/// The duration of an interval in seconds. If video is 55 seconds and interval is 15 seconds — then we will have 3 full intervals and one not full interval. Value will be used during rewind for calculations.
76+
public var intervalDuration: CGFloat = 15.0 {
5477
didSet { setNeedsDisplay() }
5578
}
5679

57-
var currentTimeDidChange: ((Double) -> ())?
80+
/// Block which will be triggered everytime currentTime value changes.
81+
public var currentTimeDidChange: ((NSTimeInterval) -> ())?
5882

5983
// MARK: - Constructors
6084

61-
init() {
85+
public init() {
6286
super.init(frame: .zero)
6387

6488
opaque = false
6589
}
6690

67-
required init?(coder aDecoder: NSCoder) {
91+
required public init?(coder aDecoder: NSCoder) {
6892
fatalError("init(coder:) has not been implemented")
6993
}
7094

7195
// MARK: - Methods
7296

73-
func currentIntervalWidth() -> CGFloat {
97+
/**
98+
Calculate current interval width. It takes two variables in count - intervalWidth and zoom.
99+
*/
100+
private func currentIntervalWidth() -> CGFloat {
74101
return intervalWidth * zoom
75102
}
76103

77-
func durationFromWidth(width: CGFloat) -> Double {
78-
return Double(width * intervalDuration / currentIntervalWidth())
104+
/**
105+
Calculates time interval in seconds from passed width.
106+
107+
- Parameter width: The distance.
108+
*/
109+
public func timeIntervalFromDistance(distance: CGFloat) -> NSTimeInterval {
110+
return NSTimeInterval(distance * intervalDuration / currentIntervalWidth())
79111
}
80112

81-
func widthFromDuration(duration: Double) -> CGFloat {
82-
return currentIntervalWidth() * CGFloat(duration) / intervalDuration
113+
/**
114+
Calculates distance from given time interval.
115+
116+
- Parameter duration: The duration of an interval.
117+
*/
118+
public func distanceFromTimeInterval(timeInterval: NSTimeInterval) -> CGFloat {
119+
return currentIntervalWidth() * CGFloat(timeInterval) / intervalDuration
83120
}
84121

85-
func rewindByWidth(width: CGFloat) {
86-
let newCurrentTime = currentTime + durationFromWidth(width)
122+
/**
123+
Rewinds by distance. Calculates interval width and adds it to the current time.
124+
125+
- Parameter distance: The distance how far it should rewind by.
126+
*/
127+
public func rewindByDistance(distance: CGFloat) {
128+
let newCurrentTime = currentTime + timeIntervalFromDistance(distance)
87129
currentTime = max(min(newCurrentTime, duration), 0.0)
88130
}
89131

90132
// MARK: - Draw
91133

92-
override func drawRect(rect: CGRect) {
134+
override public func drawRect(rect: CGRect) {
93135
super.drawRect(rect)
94136

95137
let intervalWidth = currentIntervalWidth()
96138

97-
let originX: CGFloat = bounds.width / 2.0 - widthFromDuration(currentTime)
139+
let originX: CGFloat = bounds.width / 2.0 - distanceFromTimeInterval(currentTime)
98140
let context = UIGraphicsGetCurrentContext()
99141
let lineHeight: CGFloat = 5.0
100142

@@ -111,12 +153,12 @@ class TimelineView: UIView {
111153
// Draw elapsed line
112154
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
113155

114-
let elapsedPath = UIBezierPath(roundedRect: CGRect(x: originX, y: 0.0, width: widthFromDuration(currentTime), height: lineHeight), cornerRadius: lineHeight).CGPath
156+
let elapsedPath = UIBezierPath(roundedRect: CGRect(x: originX, y: 0.0, width: distanceFromTimeInterval(currentTime), height: lineHeight), cornerRadius: lineHeight).CGPath
115157
CGContextAddPath(context, elapsedPath)
116158
CGContextFillPath(context)
117159

118160
// Draw current time dot
119-
CGContextFillEllipseInRect(context, CGRect(x: originX + widthFromDuration(initialTime), y: 7.0, width: 3.0, height: 3.0))
161+
CGContextFillEllipseInRect(context, CGRect(x: originX + distanceFromTimeInterval(initialTime), y: 7.0, width: 3.0, height: 3.0))
120162

121163
// Draw full line separators
122164
CGContextSetFillColorWithColor(context, UIColor(white: 0.0, alpha: 0.5).CGColor)

VideoViewController/VideoViewController.swift

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1+
// VideoViewController.swift
12
//
2-
// VideoViewController.swift
3-
// VideoViewControllerExample
3+
// Copyright (c) 2016 Danil Gontovnik (http://gontovnik.com/)
44
//
5-
// Created by Danil Gontovnik on 1/4/16.
6-
// Copyright © 2016 Danil Gontovnik. All rights reserved.
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
711
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
822

923
import UIKit
1024
import AVFoundation
1125

12-
class VideoViewController: UIViewController {
26+
public class VideoViewController: UIViewController {
1327

1428
// MARK: - Vars
1529

@@ -27,45 +41,49 @@ class VideoViewController: UIViewController {
2741

2842
private let rewindDimView = UIVisualEffectView()
2943
private let rewindContentView = UIView()
30-
private let rewindTimelineView = TimelineView()
31-
44+
public let rewindTimelineView = TimelineView()
3245
private let rewindPreviewShadowLayer = CALayer()
3346
private let rewindPreviewImageView = UIImageView()
3447
private let rewindCurrentTimeLabel = UILabel()
3548

36-
var rewindPreviewMaxHeight: CGFloat = 112.0 {
49+
/// Indicates the maximum height of rewindPreviewImageView. Default value is 112.
50+
public var rewindPreviewMaxHeight: CGFloat = 112.0 {
3751
didSet {
3852
assetGenerator.maximumSize = CGSize(width: CGFloat.max, height: rewindPreviewMaxHeight * UIScreen.mainScreen().scale)
3953
}
4054
}
55+
56+
/// Indicates whether player should start playing on viewDidLoad. Default is true.
57+
public var autoplays: Bool = true
4158

4259
// MARK: - Constructors
4360

44-
init(videoURL: NSURL) {
61+
/**
62+
Returns an initialized VideoViewController object
63+
64+
- Parameter videoURL: Local URL to the video asset
65+
*/
66+
public init(videoURL: NSURL) {
4567
super.init(nibName: nil, bundle: nil)
4668

4769
self.videoURL = videoURL
4870

4971
asset = AVURLAsset(URL: videoURL)
50-
5172
playerItem = AVPlayerItem(asset: asset)
52-
5373
player = AVPlayer(playerItem: playerItem)
54-
player.actionAtItemEnd = .None
55-
5674
playerLayer = AVPlayerLayer(player: player)
57-
75+
5876
assetGenerator = AVAssetImageGenerator(asset: asset)
5977
assetGenerator.maximumSize = CGSize(width: CGFloat.max, height: rewindPreviewMaxHeight * UIScreen.mainScreen().scale)
6078
}
6179

62-
required init?(coder aDecoder: NSCoder) {
80+
required public init?(coder aDecoder: NSCoder) {
6381
fatalError("init(coder:) has not been implemented")
6482
}
6583

6684
// MARK: -
6785

68-
override func loadView() {
86+
override public func loadView() {
6987
super.loadView()
7088

7189
view.backgroundColor = .blackColor()
@@ -95,7 +113,10 @@ class VideoViewController: UIViewController {
95113

96114
dispatch_async(dispatch_get_main_queue()) {
97115
strongSelf.rewindPreviewImageView.image = image
98-
strongSelf.layoutRewindPreviewImageViewIfNeeded()
116+
117+
if strongSelf.rewindPreviewImageView.bounds.size != image.size {
118+
strongSelf.viewWillLayoutSubviews()
119+
}
99120
}
100121
}
101122
}
@@ -121,15 +142,27 @@ class VideoViewController: UIViewController {
121142
rewindContentView.addSubview(rewindPreviewImageView)
122143
}
123144

124-
override func viewDidLoad() {
145+
override public func viewDidLoad() {
125146
super.viewDidLoad()
126147

127-
player.play()
148+
if autoplays {
149+
play()
150+
}
128151
}
129152

130153
// MARK: - Methods
131154

132-
func longPressed(gesture: UILongPressGestureRecognizer) {
155+
/// Resumes playback
156+
public func play() {
157+
player.play()
158+
}
159+
160+
/// Pauses playback
161+
public func pause() {
162+
player.pause()
163+
}
164+
165+
public func longPressed(gesture: UILongPressGestureRecognizer) {
133166
let location = gesture.locationInView(gesture.view!)
134167
rewindTimelineView.zoom = (location.y - rewindTimelineView.center.y - 10.0) / 30.0
135168

@@ -141,7 +174,7 @@ class VideoViewController: UIViewController {
141174
self.rewindContentView.alpha = 1.0
142175
}, completion: nil)
143176
} else if gesture.state == .Changed {
144-
rewindTimelineView.rewindByWidth(previousLocationX - location.x)
177+
rewindTimelineView.rewindByDistance(previousLocationX - location.x)
145178
} else {
146179
player.play()
147180

@@ -159,13 +192,13 @@ class VideoViewController: UIViewController {
159192
}
160193
}
161194

162-
override func prefersStatusBarHidden() -> Bool {
195+
override public func prefersStatusBarHidden() -> Bool {
163196
return true
164197
}
165198

166199
// MARK: - Layout
167200

168-
override func viewWillLayoutSubviews() {
201+
override public func viewWillLayoutSubviews() {
169202
super.viewWillLayoutSubviews()
170203

171204
playerLayer.frame = view.bounds
@@ -180,18 +213,8 @@ class VideoViewController: UIViewController {
180213
rewindPreviewImageView.frame = CGRect(x: (rewindContentView.bounds.width - rewindPreviewImageViewWidth) / 2.0, y: (rewindContentView.bounds.height - rewindPreviewMaxHeight - verticalSpacing - rewindCurrentTimeLabel.bounds.height - verticalSpacing - timelineHeight) / 2.0, width: rewindPreviewImageViewWidth, height: rewindPreviewMaxHeight)
181214
rewindCurrentTimeLabel.frame = CGRect(x: 0.0, y: rewindPreviewImageView.frame.maxY + verticalSpacing, width: rewindTimelineView.bounds.width, height: rewindCurrentTimeLabel.frame.height)
182215
rewindTimelineView.frame = CGRect(x: 0.0, y: rewindCurrentTimeLabel.frame.maxY + verticalSpacing, width: rewindContentView.bounds.width, height: timelineHeight)
183-
184-
layoutRewindPreviewImageViewIfNeeded()
185-
}
186-
187-
private func layoutRewindPreviewImageViewIfNeeded() {
188-
guard let image = rewindPreviewImageView.image where rewindPreviewImageView.bounds.size != image.size else {
189-
return
190-
}
191-
192-
rewindPreviewImageView.frame = CGRect(x: (rewindContentView.bounds.width - image.size.width) / 2.0, y: rewindPreviewImageView.frame.minY, width: image.size.width, height: rewindPreviewImageView.bounds.height)
193216
rewindPreviewShadowLayer.frame = rewindPreviewImageView.frame
194-
217+
195218
let path = UIBezierPath(roundedRect: rewindPreviewImageView.bounds, cornerRadius: 5.0).CGPath
196219
rewindPreviewShadowLayer.shadowPath = path
197220
(rewindPreviewImageView.layer.mask as! CAShapeLayer).path = path

0 commit comments

Comments
 (0)