Skip to content

Commit 21c7148

Browse files
authored
Merge pull request #80 from ospr/image-rendering
Add support for rendering waveform images outside of the view [WIP]
2 parents c146684 + 538ccda commit 21c7148

8 files changed

Lines changed: 780 additions & 330 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ All notable changes to this project will be documented in this file.
66

77
## [Master](https://github.com/fulldecent/FDWaveformView/compare/2.1.0...master)
88

9+
#### Added
10+
- Improved accuracy of waveform rendering
11+
- Added by [Kip Nicol](https://github.com/ospr)
12+
- Added support for rendering waveform images outside of a view (See `FDWaveformRenderOperation`)
13+
- Added by [Kip Nicol](https://github.com/ospr)
14+
- Added support for rendering linear waveforms
15+
- Added by [Kip Nicol](https://github.com/ospr)
16+
- Added support for changing `wavesColor` and `progressColor` after waveform was rendered
17+
- Added by [Kip Nicol](https://github.com/ospr)
18+
- Added support for updating waveform type and color to iOS Example app.
19+
- Added by [Kip Nicol](https://github.com/ospr)
20+
21+
#### Fixed
22+
- Fixed waveform rendering for large audio files
23+
- Added by [Kip Nicol](https://github.com/ospr)
24+
- Fixed bug which could prevent waveform from fitting new view size if rendering was in progress during a view resize
25+
- Added by [Kip Nicol](https://github.com/ospr)
26+
- Fixed bug which caused `waveformViewDidLoad()` to not be called after the audio file was loaded
27+
- Added by [Kip Nicol](https://github.com/ospr)
28+
- Fixed bug which caused subsequent waveform renderings for new audioURLs to never complete if there was an error with a previous render
29+
- Added by [Kip Nicol](https://github.com/ospr)
30+
- Fixed bug which could cause a crash (divide by zero error) if the view's width was 0
31+
- Added by [Kip Nicol](https://github.com/ospr)
32+
933
---
1034

1135
## [2.1.0](https://github.com/fulldecent/FDWaveformView/releases/tag/2.1.0)

Example/Source/Base.lproj/Main.storyboard

Lines changed: 82 additions & 35 deletions
Large diffs are not rendered by default.

Example/Source/ViewController.swift

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import FDWaveformView
1212

1313
class ViewController: UIViewController {
1414
@IBOutlet weak var waveform: FDWaveformView!
15+
@IBOutlet weak var logarithmicButton: UIButton!
16+
@IBOutlet weak var linearButton: UIButton!
1517

1618
fileprivate var startRendering = Date()
1719
fileprivate var endRendering = Date()
@@ -105,6 +107,30 @@ class ViewController: UIViewController {
105107
waveform.doesAllowScroll = sender.isOn
106108
}
107109

110+
@IBAction func doLinear() {
111+
/* TODO: Make this public and then use it here
112+
waveform.waveformType = .linear
113+
updateWaveformTypeButtons()
114+
*/
115+
}
116+
117+
@IBAction func doLogarithmic() {
118+
/* TODO: Make this public and then use it here
119+
waveform.waveformType = .logarithmic
120+
updateWaveformTypeButtons()
121+
*/
122+
}
123+
124+
@IBAction func doChangeColors() {
125+
let randomColor: () -> (UIColor) = {
126+
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1)
127+
}
128+
129+
UIView.animate(withDuration: 0.3, animations: {
130+
self.waveform.wavesColor = randomColor()
131+
self.waveform.progressColor = randomColor()
132+
})
133+
}
108134
override func viewDidLoad() {
109135
super.viewDidLoad()
110136
let thisBundle = Bundle(for: type(of: self))
@@ -117,30 +143,45 @@ class ViewController: UIViewController {
117143
waveform.doesAllowScrubbing = true
118144
waveform.doesAllowStretch = true
119145
waveform.doesAllowScroll = true
146+
updateWaveformTypeButtons()
147+
}
148+
149+
func updateWaveformTypeButtons() {
150+
/* TODO: Make this public and then use it here
151+
let (selectedButton, nonSelectedButton): (UIButton, UIButton) = {
152+
switch waveform.waveformType {
153+
case .linear: return (linearButton, logarithmicButton)
154+
case .logarithmic: return (logarithmicButton, linearButton)
155+
}
156+
}()
157+
selectedButton.layer.borderColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
158+
selectedButton.layer.borderWidth = 2
159+
nonSelectedButton.layer.borderWidth = 0
160+
*/
120161
}
121162
}
122163

123164
extension ViewController: FDWaveformViewDelegate {
124165
func waveformViewWillRender(_ waveformView: FDWaveformView) {
125-
self.startRendering = Date()
166+
startRendering = Date()
126167
}
127168

128169
func waveformViewDidRender(_ waveformView: FDWaveformView) {
129-
self.endRendering = Date()
130-
NSLog("FDWaveformView rendering done, took %0.3f seconds", self.endRendering.timeIntervalSince(self.startRendering))
131-
self.profileResult.append(String(format: " render %0.3f ", self.endRendering.timeIntervalSince(self.startRendering)))
170+
endRendering = Date()
171+
NSLog("FDWaveformView rendering done, took %0.3f seconds", endRendering.timeIntervalSince(startRendering))
172+
profileResult.append(String(format: " render %0.3f ", endRendering.timeIntervalSince(startRendering)))
132173
UIView.animate(withDuration: 0.25, animations: {() -> Void in
133174
waveformView.alpha = 1.0
134175
})
135176
}
136177

137178
func waveformViewWillLoad(_ waveformView: FDWaveformView) {
138-
self.startLoading = Date()
179+
startLoading = Date()
139180
}
140181

141182
func waveformViewDidLoad(_ waveformView: FDWaveformView) {
142-
self.endLoading = Date()
143-
NSLog("FDWaveformView loading done, took %f seconds", self.endLoading.timeIntervalSince(self.startLoading))
144-
self.profileResult.append(" load \(self.endLoading.timeIntervalSince(self.startLoading))")
183+
endLoading = Date()
184+
NSLog("FDWaveformView loading done, took %0.3f seconds", endLoading.timeIntervalSince(startLoading))
185+
profileResult.append(String(format: " load %0.3f ", endLoading.timeIntervalSince(startLoading)))
145186
}
146187
}

FDWaveformView.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/* Begin PBXBuildFile section */
1010
D94BE1CF1CCEBFD80042282A /* FDWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D94BE1CE1CCEBFD80042282A /* FDWaveformView.swift */; };
11+
D993BD581EBA3D110022A881 /* FDAudioContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D993BD571EBA3D110022A881 /* FDAudioContext.swift */; };
12+
D993BD5A1EBA3F570022A881 /* FDWaveformRenderOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D993BD591EBA3F570022A881 /* FDWaveformRenderOperation.swift */; };
1113
D9FEF4BF1CCEBC0F0013FBDD /* FDWaveformView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9FEF4B41CCEBC0D0013FBDD /* FDWaveformView.framework */; };
1214
D9FEF4C41CCEBC100013FBDD /* FDWaveformViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9FEF4C31CCEBC100013FBDD /* FDWaveformViewTests.swift */; };
1315
/* End PBXBuildFile section */
@@ -24,6 +26,8 @@
2426

2527
/* Begin PBXFileReference section */
2628
D94BE1CE1CCEBFD80042282A /* FDWaveformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FDWaveformView.swift; sourceTree = "<group>"; };
29+
D993BD571EBA3D110022A881 /* FDAudioContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FDAudioContext.swift; sourceTree = "<group>"; };
30+
D993BD591EBA3F570022A881 /* FDWaveformRenderOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FDWaveformRenderOperation.swift; sourceTree = "<group>"; };
2731
D9FEF4B41CCEBC0D0013FBDD /* FDWaveformView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FDWaveformView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2832
D9FEF4B91CCEBC0D0013FBDD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = "<group>"; };
2933
D9FEF4BE1CCEBC0F0013FBDD /* FDWaveformViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FDWaveformViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -81,6 +85,8 @@
8185
isa = PBXGroup;
8286
children = (
8387
D94BE1CE1CCEBFD80042282A /* FDWaveformView.swift */,
88+
D993BD571EBA3D110022A881 /* FDAudioContext.swift */,
89+
D993BD591EBA3F570022A881 /* FDWaveformRenderOperation.swift */,
8490
);
8591
path = Source;
8692
sourceTree = "<group>";
@@ -203,6 +209,8 @@
203209
isa = PBXSourcesBuildPhase;
204210
buildActionMask = 2147483647;
205211
files = (
212+
D993BD581EBA3D110022A881 /* FDAudioContext.swift in Sources */,
213+
D993BD5A1EBA3F570022A881 /* FDWaveformRenderOperation.swift in Sources */,
206214
D94BE1CF1CCEBFD80042282A /* FDWaveformView.swift in Sources */,
207215
);
208216
runOnlyForDeploymentPostprocessing = 0;

Source/FDAudioContext.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Copyright 2013 - 2017, William Entriken and the FDWaveformView contributors.
3+
//
4+
import UIKit
5+
import AVFoundation
6+
7+
/// Holds audio information used for building waveforms
8+
final class FDAudioContext {
9+
10+
/// The audio asset URL used to load the context
11+
public let audioURL: URL
12+
13+
/// Total number of samples in loaded asset
14+
public let totalSamples: Int
15+
16+
/// Loaded asset
17+
public let asset: AVAsset
18+
19+
// Loaded assetTrack
20+
public let assetTrack: AVAssetTrack
21+
22+
private init(audioURL: URL, totalSamples: Int, asset: AVAsset, assetTrack: AVAssetTrack) {
23+
self.audioURL = audioURL
24+
self.totalSamples = totalSamples
25+
self.asset = asset
26+
self.assetTrack = assetTrack
27+
}
28+
29+
public static func load(fromAudioURL audioURL: URL, completionHandler: @escaping (_ audioContext: FDAudioContext?) -> ()) {
30+
let asset = AVURLAsset(url: audioURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: NSNumber(value: true as Bool)])
31+
32+
guard let assetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first else {
33+
NSLog("FDWaveformView failed to load AVAssetTrack")
34+
completionHandler(nil)
35+
return
36+
}
37+
38+
asset.loadValuesAsynchronously(forKeys: ["duration"]) {
39+
var error: NSError?
40+
let status = asset.statusOfValue(forKey: "duration", error: &error)
41+
switch status {
42+
case .loaded:
43+
guard
44+
let audioFormatDesc = assetTrack.formatDescriptions.first,
45+
let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(audioFormatDesc as! CMAudioFormatDescription) // TODO: Can this be safer?
46+
else { break }
47+
48+
let totalSamples = Int((asbd.pointee.mSampleRate) * Float64(asset.duration.value) / Float64(asset.duration.timescale))
49+
let audioContext = FDAudioContext(audioURL: audioURL, totalSamples: totalSamples, asset: asset, assetTrack: assetTrack)
50+
completionHandler(audioContext)
51+
return
52+
53+
case .failed, .cancelled, .loading, .unknown:
54+
print("FDWaveformView could not load asset: \(error?.localizedDescription ?? "Unknown error")")
55+
}
56+
57+
completionHandler(nil)
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)