├── Cartfile
├── AVSExample
├── Images.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── AppDelegate.swift
├── Config.swift
├── Info.plist
├── login.html
├── SimpleWebServer.swift
├── SimplePCMRecorder.swift
├── ViewController.swift
├── AVSUploader.swift
└── Base.lproj
│ └── Main.storyboard
├── AVSExample.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── .gitignore
├── LICENSE
└── README.md
/Cartfile:
--------------------------------------------------------------------------------
1 | github "swisspol/GCDWebServer" ~> 3.2.5
2 |
--------------------------------------------------------------------------------
/AVSExample/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/AVSExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // AVSExample
4 | //
5 |
6 |
7 | import Cocoa
8 |
9 | @NSApplicationMain
10 | class AppDelegate: NSObject, NSApplicationDelegate {
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/AVSExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # Carthage
21 | Carthage/Checkouts
22 | Carthage/Build
23 | Cartfile.resolved
24 |
--------------------------------------------------------------------------------
/AVSExample/Config.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Config.swift
3 | // AVSExample
4 | //
5 |
6 | import Foundation
7 |
8 | struct Config {
9 |
10 | struct LoginWithAmazon {
11 | static let ClientId = ""
12 | static let ProductId = ""
13 | static let DeviceSerialNumber = ""
14 | }
15 |
16 | struct Debug {
17 | static let General = false
18 | static let Errors = true
19 | static let HTTPRequest = false
20 | static let HTTPResponse = false
21 | }
22 |
23 | struct Error {
24 | static let ErrorDomain = "net.ioncannon.SimplePCMRecorderError"
25 |
26 | static let PCMSetupIncompleteErrorCode = 1
27 |
28 | static let AVSUploaderSetupIncompleteErrorCode = 2
29 | static let AVSAPICallErrorCode = 3
30 | static let AVSResponseBorderParseErrorCode = 4
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Carson McDonald
4 |
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:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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 THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/AVSExample/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/AVSExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2015 TEst. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 | CFBundleURLTypes
34 |
35 |
36 | CFBundleURLName
37 | AVSExample
38 | CFBundleURLSchemes
39 |
40 | avsexample
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/AVSExample/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 | Login:
22 |
23 |
24 |
25 |
26 |
27 |
28 | Logout: Logout
29 |
30 |
31 |
46 |
47 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AVSExample-Swift
2 | This is an [Alexa Voice Service](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service) example using Swift specifically for the Mac but the general concept should work on iOS as well. It requires Swift 2 from XCode 7 or later.
3 |
4 | Before getting started make sure you have read [getting started with Alexa Voice Service](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/getting-started-with-the-alexa-voice-service).
5 |
6 | [Carthage](https://github.com/Carthage/Carthage) is used for dependancies. After cloning go into the root directory and run:
7 |
8 | ```
9 | carthage bootstrap
10 | ```
11 |
12 | You will need to fill out the following three items found in Config.swift before running:
13 |
14 | ```
15 | struct LoginWithAmazon {
16 | static let ClientId = ""
17 | static let ProductId = ""
18 | static let DeviceSerialNumber = ""
19 | }
20 | ```
21 |
22 | If you follow the AVS getting started guide from above then the *ClientId* is set up in the *Select or Create a Security Profile* section, the *ProductId* is called *Device Type ID* and set up in the *Device Type Info* section and the *DeviceSerialNumber* can be anything as long as it is unique for the device type (something like "1000-0000-0000-0000" for example).
23 |
24 | Once configured you can run the application and configure the authentication. The configure button will open a Login with Amazon web page that will allow you to authorize the application to use Alexa Voice Service. Once complete there will be a link to take you back to the application where you can use the record button to query Alexa.
25 |
26 | ## Structure
27 |
28 | Authorization is performed using the *Implicit Grant* method described in the [Authorizing Your Alexa-enabled Product from a Website](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/authorizing-your-alexa-enabled-product-from-a-website) documentation. The application runs a simple web server derived from [GCDWebServer](https://github.com/swisspol/GCDWebServer), see *SimpleWebServer.swift* and *login.html* for more information.
29 |
30 | The code for recording the request audio can be found in *SimplePCMRecorder.swift*, it is hard coded to the correct format for AVS. As of now this is the only format that will work with AVS so if you change things in this file be prepared for the processing to potentially fail.
31 |
32 | The code for uploading the request audio as well as constructing the proper request can be found in *AVSUploader.swift*.
33 |
34 | The code in *ViewController.swift* glues together the authorization, recording and uploading.
35 |
36 | ## Potential Enhancements
37 |
38 | - Save config data to prefs, save auth token info to prefs
39 | - Display auth token expiration time on the screen
40 | - Upload to AVS as recording is in progress
41 | - Add a max length for the recording
42 | - Put in task bar, add a global shortcut key to record
43 |
--------------------------------------------------------------------------------
/AVSExample/SimpleWebServer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleWebServer.swift
3 | // AVSExample
4 | //
5 |
6 | import Foundation
7 | import GCDWebServers
8 |
9 | protocol SimpleWebServerDelegate {
10 | func startupComplete(webServerURL: NSURL)
11 | func configurationComplete(tokenExpirationTime: NSDate, currentAccessToken: String)
12 | }
13 |
14 | class SimpleWebServer: NSObject, GCDWebServerDelegate {
15 |
16 | static let instance = SimpleWebServer()
17 |
18 | var delegate: SimpleWebServerDelegate?
19 |
20 | private let webServer = GCDWebServer()
21 | private var loginHTML: String?
22 |
23 | func startWebServer() {
24 |
25 | self.webServer.delegate = self
26 |
27 | if loginHTML == nil {
28 | if let rootPath = NSBundle.mainBundle().resourcePath, let loginData = NSData(contentsOfFile: "\(rootPath)/login.html") {
29 | var html = NSString(data: loginData, encoding: NSUTF8StringEncoding)
30 |
31 | html = html?.stringByReplacingOccurrencesOfString("#{client_id}", withString: Config.LoginWithAmazon.ClientId)
32 | html = html?.stringByReplacingOccurrencesOfString("#{device_serial_number}", withString: Config.LoginWithAmazon.DeviceSerialNumber)
33 | html = html?.stringByReplacingOccurrencesOfString("#{product_id}", withString: Config.LoginWithAmazon.ProductId)
34 |
35 | loginHTML = html as String?
36 |
37 | } else {
38 | loginHTML = "Error loading login html."
39 | }
40 | }
41 |
42 | webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
43 | return GCDWebServerDataResponse(HTML:self.loginHTML)
44 | })
45 |
46 | webServer.addHandlerForMethod("GET", path: "/complete", requestClass: GCDWebServerRequest.self) { (request:GCDWebServerRequest!) -> GCDWebServerResponse! in
47 |
48 | if let expiresIn = request.query["expires_in"] as? String, let accessToken = request.query["access_token"] as? String {
49 |
50 | var tokenExpirationTime: NSDate?
51 | var currentAccessToken: String?
52 |
53 | if let eid = Double(expiresIn) {
54 | tokenExpirationTime = NSDate().dateByAddingTimeInterval(eid)
55 | } else {
56 | tokenExpirationTime = NSDate()
57 | }
58 | currentAccessToken = accessToken
59 |
60 | if tokenExpirationTime != nil && currentAccessToken != nil {
61 | self.delegate?.configurationComplete(tokenExpirationTime!, currentAccessToken: currentAccessToken!)
62 | }
63 |
64 | return GCDWebServerDataResponse(HTML: "Login complete, you can go back to the app now.")
65 |
66 | } else {
67 | return GCDWebServerDataResponse(HTML: "Error logging in, Try again")
68 | }
69 |
70 | }
71 |
72 | webServer.startWithPort(8777, bonjourName: "GCD Web Server")
73 |
74 | }
75 |
76 | //
77 | // GCDWebServerDelegate Impl
78 | //
79 |
80 | func webServerDidStart(server: GCDWebServer!) {
81 | delegate?.startupComplete(server.serverURL)
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/AVSExample/SimplePCMRecorder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimplePCMRecorder.swift
3 | // AVSExample
4 | //
5 |
6 | import Foundation
7 | import CoreAudio
8 | import AudioToolbox
9 |
10 | struct RecorderState {
11 | var setupComplete: Bool
12 | var dataFormat: AudioStreamBasicDescription
13 | var queue: UnsafeMutablePointer
14 | var buffers: [AudioQueueBufferRef]
15 | var recordFile: AudioFileID
16 | var bufferByteSize: UInt32
17 | var currentPacket: Int64
18 | var isRunning: Bool
19 | var recordPacket: Int64
20 | var errorHandler: ((error:NSError) -> Void)?
21 | }
22 |
23 | class SimplePCMRecorder {
24 |
25 | private var recorderState: RecorderState
26 |
27 | init(numberBuffers:Int) {
28 | self.recorderState = RecorderState(
29 | setupComplete: false,
30 | dataFormat: AudioStreamBasicDescription(),
31 | queue: UnsafeMutablePointer.alloc(1),
32 | buffers: Array(count: numberBuffers, repeatedValue: nil),
33 | recordFile: AudioFileID(),
34 | bufferByteSize: 0,
35 | currentPacket: 0,
36 | isRunning: false,
37 | recordPacket: 0,
38 | errorHandler: nil)
39 | }
40 |
41 | deinit {
42 | self.recorderState.queue.dealloc(1)
43 | }
44 |
45 | func setupForRecording(outputFileName:String, sampleRate:Float64, channels:UInt32, bitsPerChannel:UInt32, errorHandler: ((error:NSError) -> Void)?) throws {
46 | self.recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM
47 | self.recorderState.dataFormat.mSampleRate = sampleRate
48 | self.recorderState.dataFormat.mChannelsPerFrame = channels
49 | self.recorderState.dataFormat.mBitsPerChannel = bitsPerChannel
50 | self.recorderState.dataFormat.mFramesPerPacket = 1
51 | self.recorderState.dataFormat.mBytesPerFrame = self.recorderState.dataFormat.mChannelsPerFrame * (self.recorderState.dataFormat.mBitsPerChannel / 8)
52 | self.recorderState.dataFormat.mBytesPerPacket = self.recorderState.dataFormat.mBytesPerFrame * self.recorderState.dataFormat.mFramesPerPacket
53 |
54 | self.recorderState.dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked
55 |
56 | self.recorderState.errorHandler = errorHandler
57 |
58 | try osReturningCall { AudioFileCreateWithURL(NSURL(fileURLWithPath: outputFileName), kAudioFileWAVEType, &self.recorderState.dataFormat, AudioFileFlags.DontPageAlignAudioData.union(.EraseFile), &self.recorderState.recordFile) }
59 |
60 | self.recorderState.setupComplete = true
61 | }
62 |
63 | func startRecording() throws {
64 |
65 | guard self.recorderState.setupComplete else { throw NSError(domain: Config.Error.ErrorDomain, code: Config.Error.PCMSetupIncompleteErrorCode, userInfo: [NSLocalizedDescriptionKey : "Setup needs to be called before starting"]) }
66 |
67 | let osAQNI = AudioQueueNewInput(&self.recorderState.dataFormat, { (inUserData:UnsafeMutablePointer, inAQ:AudioQueueRef, inBuffer:AudioQueueBufferRef, inStartTime:UnsafePointer, inNumPackets:UInt32, inPacketDesc:UnsafePointer) -> Void in
68 |
69 | let internalRSP = unsafeBitCast(inUserData, UnsafeMutablePointer.self)
70 |
71 | if inNumPackets > 0 {
72 | var packets = inNumPackets
73 |
74 | let os = AudioFileWritePackets(internalRSP.memory.recordFile, false, inBuffer.memory.mAudioDataByteSize, inPacketDesc, internalRSP.memory.recordPacket, &packets, inBuffer.memory.mAudioData)
75 | if os != 0 && internalRSP.memory.errorHandler != nil {
76 | internalRSP.memory.errorHandler!(error:NSError(domain: NSOSStatusErrorDomain, code: Int(os), userInfo: nil))
77 | }
78 |
79 | internalRSP.memory.recordPacket += Int64(packets)
80 | }
81 |
82 | if internalRSP.memory.isRunning {
83 | let os = AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil)
84 | if os != 0 && internalRSP.memory.errorHandler != nil {
85 | internalRSP.memory.errorHandler!(error:NSError(domain: NSOSStatusErrorDomain, code: Int(os), userInfo: nil))
86 | }
87 | }
88 |
89 | }, &self.recorderState, nil, nil, 0, self.recorderState.queue)
90 |
91 | guard osAQNI == 0 else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(osAQNI), userInfo: nil) }
92 |
93 | let bufferByteSize = try self.computeRecordBufferSize(self.recorderState.dataFormat, seconds: 0.5)
94 | for (var i = 0; i < self.recorderState.buffers.count; ++i) {
95 | try osReturningCall { AudioQueueAllocateBuffer(self.recorderState.queue.memory, UInt32(bufferByteSize), &self.recorderState.buffers[i]) }
96 |
97 | try osReturningCall { AudioQueueEnqueueBuffer(self.recorderState.queue.memory, self.recorderState.buffers[i], 0, nil) }
98 | }
99 |
100 | try osReturningCall { AudioQueueStart(self.recorderState.queue.memory, nil) }
101 |
102 | self.recorderState.isRunning = true
103 | }
104 |
105 | func stopRecording() throws {
106 | self.recorderState.isRunning = false
107 |
108 | try osReturningCall { AudioQueueStop(self.recorderState.queue.memory, true) }
109 | try osReturningCall { AudioQueueDispose(self.recorderState.queue.memory, true) }
110 | try osReturningCall { AudioFileClose(self.recorderState.recordFile) }
111 | }
112 |
113 | private func computeRecordBufferSize(format:AudioStreamBasicDescription, seconds:Double) throws -> Int {
114 |
115 | let framesNeededForBufferTime = Int(ceil(seconds * format.mSampleRate))
116 |
117 | if format.mBytesPerFrame > 0 {
118 | return framesNeededForBufferTime * Int(format.mBytesPerFrame)
119 | } else {
120 | var maxPacketSize = UInt32(0)
121 |
122 | if format.mBytesPerPacket > 0 {
123 | maxPacketSize = format.mBytesPerPacket
124 | } else {
125 | try self.getAudioQueueProperty(kAudioQueueProperty_MaximumOutputPacketSize, value: &maxPacketSize)
126 | }
127 |
128 | var packets = 0
129 | if format.mFramesPerPacket > 0 {
130 | packets = framesNeededForBufferTime / Int(format.mFramesPerPacket)
131 | } else {
132 | packets = framesNeededForBufferTime
133 | }
134 |
135 | if packets == 0 {
136 | packets = 1
137 | }
138 |
139 | return packets * Int(maxPacketSize)
140 | }
141 |
142 | }
143 |
144 | private func osReturningCall(osCall: () -> OSStatus) throws {
145 | let os = osCall()
146 | if os != 0 {
147 | throw NSError(domain: NSOSStatusErrorDomain, code: Int(os), userInfo: nil)
148 | }
149 | }
150 |
151 | private func getAudioQueueProperty(propertyId:AudioQueuePropertyID, inout value:T) throws {
152 |
153 | let propertySize = UnsafeMutablePointer.alloc(1)
154 | propertySize.memory = UInt32(sizeof(T))
155 |
156 | let os = AudioQueueGetProperty(self.recorderState.queue.memory,
157 | propertyId,
158 | &value,
159 | propertySize)
160 |
161 | propertySize.dealloc(1)
162 |
163 | guard os == 0 else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(os), userInfo: nil) }
164 |
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/AVSExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // AVSExample
4 | //
5 |
6 | import Cocoa
7 | import AVFoundation
8 | import GCDWebServers
9 |
10 | class ViewController: NSViewController, AVAudioPlayerDelegate, SimpleWebServerDelegate {
11 |
12 | @IBOutlet weak var recordButton: NSButton!
13 | @IBOutlet weak var statusLabel: NSTextField!
14 | @IBOutlet weak var configureButton: NSButton!
15 |
16 | private var webServerURL: NSURL?
17 | private var currentAccessToken: String?
18 | private var tokenExpirationTime: NSDate?
19 |
20 | private var isRecording = false
21 |
22 | private var simplePCMRecorder: SimplePCMRecorder
23 |
24 | private let tempFilename = "\(NSTemporaryDirectory())avsexample.wav"
25 |
26 | private var player: AVAudioPlayer?
27 |
28 | required init?(coder: NSCoder) {
29 | self.simplePCMRecorder = SimplePCMRecorder(numberBuffers: 1)
30 |
31 | super.init(coder: coder)
32 | }
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | NSAppleEventManager.sharedAppleEventManager().setEventHandler(self, andSelector: "handleURLEvent", forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
38 |
39 | self.recordButton.enabled = false
40 | self.statusLabel.stringValue = "Starting"
41 |
42 | self.configureButton.enabled = false
43 |
44 | self.recordButton.continuous = true
45 | self.recordButton.setPeriodicDelay(0.075, interval: 0.075)
46 |
47 | SimpleWebServer.instance.delegate = self
48 | SimpleWebServer.instance.startWebServer()
49 |
50 | // Have the recorder create a first recording that will get tossed so it starts faster later
51 | try! self.simplePCMRecorder.setupForRecording(tempFilename, sampleRate:16000, channels:1, bitsPerChannel:16, errorHandler: nil)
52 | try! self.simplePCMRecorder.startRecording()
53 | try! self.simplePCMRecorder.stopRecording()
54 | self.simplePCMRecorder = SimplePCMRecorder(numberBuffers: 1)
55 | }
56 |
57 | @IBAction func recordAction(recordButton: NSButton) {
58 |
59 | if recordButton.state == NSOffState {
60 | if !self.isRecording {
61 | self.isRecording = true
62 |
63 | self.simplePCMRecorder = SimplePCMRecorder(numberBuffers: 1)
64 | try! self.simplePCMRecorder.setupForRecording(tempFilename, sampleRate:16000, channels:1, bitsPerChannel:16, errorHandler: { (error:NSError) -> Void in
65 | print(error)
66 | try! self.simplePCMRecorder.stopRecording()
67 | })
68 |
69 | try! self.simplePCMRecorder.startRecording()
70 |
71 | self.statusLabel.stringValue = "Recording"
72 | }
73 | } else {
74 | if self.isRecording {
75 | self.isRecording = false
76 | recordButton.state = NSOffState
77 |
78 | self.recordButton.enabled = false
79 |
80 | try! self.simplePCMRecorder.stopRecording()
81 |
82 | self.statusLabel.stringValue = "Uploading recording"
83 |
84 | self.upload()
85 | }
86 | }
87 |
88 | }
89 |
90 | @IBAction func configureAction(sender: AnyObject) {
91 | NSWorkspace.sharedWorkspace().openURL(self.webServerURL!)
92 | }
93 |
94 |
95 | private func upload() {
96 | let uploader = AVSUploader()
97 |
98 | uploader.authToken = self.currentAccessToken
99 |
100 | uploader.jsonData = self.createMeatadata()
101 |
102 | uploader.audioData = NSData(contentsOfFile: tempFilename)!
103 |
104 | uploader.errorHandler = { (error:NSError) in
105 | if Config.Debug.Errors {
106 | print("Upload error: \(error)")
107 | }
108 |
109 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
110 | self.statusLabel.stringValue = "Upload error: \(error.localizedDescription)"
111 | self.recordButton.enabled = true
112 | })
113 | }
114 |
115 | uploader.progressHandler = { (progress:Double) in
116 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
117 | if progress < 100.0 {
118 | self.statusLabel.stringValue = String(format: "Upload progress: %d", progress)
119 | } else {
120 | self.statusLabel.stringValue = "Waiting for response"
121 | }
122 | })
123 | }
124 |
125 | uploader.successHandler = { (data:NSData, parts:[PartData]) -> Void in
126 |
127 | for part in parts {
128 | if part.headers["Content-Type"] == "application/json" {
129 | if Config.Debug.General {
130 | print(NSString(data: part.data, encoding: NSUTF8StringEncoding))
131 | }
132 | } else if part.headers["Content-Type"] == "audio/mpeg" {
133 | do {
134 | self.player = try AVAudioPlayer(data: part.data)
135 | self.player?.delegate = self
136 | self.player?.play()
137 |
138 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
139 | self.statusLabel.stringValue = "Playing response"
140 | })
141 | } catch let error {
142 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
143 | self.statusLabel.stringValue = "Playing error: \(error)"
144 | self.recordButton.enabled = true
145 | })
146 | }
147 | }
148 | }
149 |
150 | }
151 |
152 | try! uploader.start()
153 | }
154 |
155 | private func createMeatadata() -> String? {
156 | var rootElement = [String:AnyObject]()
157 |
158 | let deviceContextPayload = ["streamId":"", "offsetInMilliseconds":"0", "playerActivity":"IDLE"]
159 | let deviceContext = ["name":"playbackState", "namespace":"AudioPlayer", "payload":deviceContextPayload]
160 | rootElement["messageHeader"] = ["deviceContext":[deviceContext]]
161 |
162 | let deviceProfile = ["profile":"doppler-scone", "locale":"en-us", "format":"audio/L16; rate=16000; channels=1"]
163 | rootElement["messageBody"] = deviceProfile
164 |
165 | let data = try! NSJSONSerialization.dataWithJSONObject(rootElement, options: NSJSONWritingOptions(rawValue: 0))
166 |
167 | return NSString(data: data, encoding: NSUTF8StringEncoding) as String?
168 | }
169 |
170 | //
171 | // SimpleWebServerDelegate Impl
172 | //
173 |
174 | func startupComplete(webServerURL: NSURL) {
175 | // Always force localhost as the host
176 | self.webServerURL = NSURL(scheme: webServerURL.scheme, host: "localhost:\(webServerURL.port!)", path: webServerURL.path!)
177 |
178 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
179 | self.statusLabel.stringValue = "Configuration needed"
180 | self.configureButton.enabled = true
181 | })
182 | }
183 |
184 | func configurationComplete(tokenExpirationTime: NSDate, currentAccessToken: String) {
185 | self.currentAccessToken = currentAccessToken
186 | self.tokenExpirationTime = tokenExpirationTime
187 |
188 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
189 | self.statusLabel.stringValue = "Ready"
190 | self.recordButton.enabled = true
191 | })
192 | }
193 |
194 | //
195 | // AVAudioPlayerDelegate Impl
196 | //
197 |
198 | func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
199 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
200 | self.statusLabel.stringValue = "Ready"
201 | self.recordButton.enabled = true
202 | })
203 | }
204 |
205 | func audioPlayerDecodeErrorDidOccur(player: AVAudioPlayer, error: NSError?) {
206 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
207 | self.statusLabel.stringValue = "Player error: \(error)"
208 | self.recordButton.enabled = true
209 | })
210 | }
211 |
212 | //
213 | // Handle app URL
214 | //
215 |
216 | func handleURLEvent() {
217 | if self.currentAccessToken != nil && self.tokenExpirationTime != nil {
218 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
219 | self.statusLabel.stringValue = "Ready"
220 | self.recordButton.enabled = true
221 | })
222 | } else {
223 | dispatch_async(dispatch_get_main_queue(), { () -> Void in
224 | self.statusLabel.stringValue = "Configuration error"
225 | self.recordButton.enabled = false
226 | })
227 | }
228 | }
229 | }
230 |
231 |
--------------------------------------------------------------------------------
/AVSExample/AVSUploader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVSUploader.swift
3 | // AVSExample
4 | //
5 |
6 | import Foundation
7 |
8 | struct PartData {
9 | var headers: [String:String]
10 | var data: NSData
11 | }
12 |
13 | class AVSUploader: NSObject, NSURLSessionTaskDelegate {
14 |
15 | var authToken:String?
16 | var jsonData:String?
17 | var audioData:NSData?
18 |
19 | var errorHandler: ((error:NSError) -> Void)?
20 | var progressHandler: ((progress:Double) -> Void)?
21 | var successHandler: ((data:NSData, parts:[PartData]) -> Void)?
22 |
23 | private var session: NSURLSession!
24 |
25 | func start() throws {
26 | if self.authToken == nil || self.jsonData == nil || self.audioData == nil {
27 | throw NSError(domain: Config.Error.ErrorDomain, code: Config.Error.AVSUploaderSetupIncompleteErrorCode, userInfo: [NSLocalizedDescriptionKey : "AVS upload options not set"])
28 | }
29 |
30 | if self.session == nil {
31 | self.session = NSURLSession(configuration: NSURLSession.sharedSession().configuration, delegate: self, delegateQueue: nil)
32 | }
33 |
34 | self.postRecording(self.authToken!, jsonData: self.jsonData!, audioData: self.audioData!)
35 | }
36 |
37 | private func parseResponse(data:NSData, boundry:String) -> [PartData] {
38 |
39 | let innerBoundry = "\(boundry)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!
40 | let endBoundry = "\r\n\(boundry)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!
41 |
42 | var innerRanges = [NSRange]()
43 | var lastStartingLocation = 0
44 |
45 | var boundryRange = data.rangeOfData(innerBoundry, options: NSDataSearchOptions(), range: NSMakeRange(lastStartingLocation, data.length))
46 | while(boundryRange.location != NSNotFound) {
47 |
48 | lastStartingLocation = boundryRange.location + boundryRange.length
49 | boundryRange = data.rangeOfData(innerBoundry, options: NSDataSearchOptions(), range: NSMakeRange(lastStartingLocation, data.length - lastStartingLocation))
50 |
51 | if boundryRange.location != NSNotFound {
52 | innerRanges.append(NSMakeRange(lastStartingLocation, boundryRange.location - innerBoundry.length))
53 | } else {
54 | innerRanges.append(NSMakeRange(lastStartingLocation, data.length - lastStartingLocation))
55 | }
56 | }
57 |
58 | var partData = [PartData]()
59 |
60 | for innerRange in innerRanges {
61 | let innerData = data.subdataWithRange(innerRange)
62 |
63 | let headerRange = innerData.rangeOfData("\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!, options: NSDataSearchOptions(), range: NSMakeRange(0, innerRange.length))
64 |
65 | var headers = [String:String]()
66 | if let headerData = NSString(data: innerData.subdataWithRange(NSMakeRange(0, headerRange.location)), encoding: NSUTF8StringEncoding) as? String {
67 | let headerLines = headerData.characters.split{$0 == "\r\n"}.map{String($0)}
68 | for headerLine in headerLines {
69 | let headerSplit = headerLine.characters.split{ $0 == ":" }.map{String($0)}
70 | headers[headerSplit[0]] = headerSplit[1].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
71 | }
72 | }
73 |
74 | let startLocation = headerRange.location + headerRange.length
75 | let contentData = innerData.subdataWithRange(NSMakeRange(startLocation, innerRange.length - startLocation))
76 |
77 | let endContentRange = contentData.rangeOfData(endBoundry, options: NSDataSearchOptions(), range: NSMakeRange(0, contentData.length))
78 | if endContentRange.location != NSNotFound {
79 | partData.append(PartData(headers: headers, data: contentData.subdataWithRange(NSMakeRange(0, endContentRange.location))))
80 | } else {
81 | partData.append(PartData(headers: headers, data: contentData))
82 | }
83 | }
84 |
85 | return partData
86 | }
87 |
88 | private func postRecording(authToken:String, jsonData:String, audioData:NSData) {
89 | let request = NSMutableURLRequest(URL: NSURL(string: "https://access-alexa-na.amazon.com/v1/avs/speechrecognizer/recognize")!)
90 | request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
91 | request.HTTPShouldHandleCookies = false
92 | request.timeoutInterval = 60
93 | request.HTTPMethod = "POST"
94 |
95 | request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
96 |
97 | let boundry = NSUUID().UUIDString
98 | let contentType = "multipart/form-data; boundary=\(boundry)"
99 |
100 | request.setValue(contentType, forHTTPHeaderField: "Content-Type")
101 |
102 | let bodyData = NSMutableData()
103 |
104 | bodyData.appendData("--\(boundry)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
105 | bodyData.appendData("Content-Disposition: form-data; name=\"metadata\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
106 | bodyData.appendData("Content-Type: application/json; charset=UTF-8\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
107 | bodyData.appendData(jsonData.dataUsingEncoding(NSUTF8StringEncoding)!)
108 | bodyData.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
109 |
110 | bodyData.appendData("--\(boundry)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
111 | bodyData.appendData("Content-Disposition: form-data; name=\"audio\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
112 | bodyData.appendData("Content-Type: audio/L16; rate=16000; channels=1\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
113 | bodyData.appendData(audioData)
114 | bodyData.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
115 |
116 | bodyData.appendData("--\(boundry)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
117 |
118 | let uploadTask = self.session.uploadTaskWithRequest(request, fromData: bodyData) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
119 |
120 | self.progressHandler?(progress: 100.0)
121 |
122 | if let e = error {
123 | self.errorHandler?(error: e)
124 | } else {
125 | if let httpResponse = response as? NSHTTPURLResponse {
126 |
127 | if httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299 {
128 | if let responseData = data, let contentTypeHeader = httpResponse.allHeaderFields["Content-Type"] {
129 | var boundry: String?
130 | let ctbRange = contentTypeHeader.rangeOfString("boundary=.*?;", options: .RegularExpressionSearch)
131 | if ctbRange.location != NSNotFound {
132 | let boundryNSS = contentTypeHeader.substringWithRange(ctbRange) as NSString
133 | boundry = boundryNSS.substringWithRange(NSRange(location: 9, length: boundryNSS.length - 10))
134 | }
135 |
136 | if let b = boundry {
137 | self.successHandler?(data: responseData, parts:self.parseResponse(responseData, boundry: b))
138 | } else {
139 | self.errorHandler?(error: NSError(domain: Config.Error.ErrorDomain, code: Config.Error.AVSResponseBorderParseErrorCode, userInfo: [NSLocalizedDescriptionKey : "Could not find boundry in AVS response"]))
140 | }
141 | }
142 | } else {
143 | var message: NSString?
144 | if data != nil {
145 | do {
146 | if let errorDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue: 0)) as? [String:AnyObject], let errorValue = errorDictionary["error"] as? [String:String], let errorMessage = errorValue["message"] {
147 |
148 | message = errorMessage
149 |
150 | } else {
151 | message = NSString(data: data!, encoding: NSUTF8StringEncoding)
152 | }
153 | } catch {
154 | message = NSString(data: data!, encoding: NSUTF8StringEncoding)
155 | }
156 | }
157 | let finalMessage = message == nil ? "" : message!
158 | self.errorHandler?(error: NSError(domain: Config.Error.ErrorDomain, code: Config.Error.AVSAPICallErrorCode, userInfo: [NSLocalizedDescriptionKey : "AVS error: \(httpResponse.statusCode) - \(finalMessage)"]))
159 | }
160 |
161 | }
162 | }
163 | }
164 |
165 | uploadTask.resume()
166 | }
167 |
168 | func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
169 |
170 | self.progressHandler?(progress:Double(Double(totalBytesSent) / Double(totalBytesExpectedToSend)) * 100.0)
171 |
172 | }
173 | }
--------------------------------------------------------------------------------
/AVSExample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
106 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/AVSExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B6017A761BA46E4E006DABB5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6017A751BA46E4E006DABB5 /* AppDelegate.swift */; };
11 | B6017A781BA46E4E006DABB5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6017A771BA46E4E006DABB5 /* ViewController.swift */; };
12 | B6017A7A1BA46E4E006DABB5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B6017A791BA46E4E006DABB5 /* Images.xcassets */; };
13 | B6017A7D1BA46E4E006DABB5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6017A7B1BA46E4E006DABB5 /* Main.storyboard */; };
14 | B6798B261BA9C6DB0020B47A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6798B251BA9C6DB0020B47A /* AVFoundation.framework */; };
15 | B6798B281BA9C9D10020B47A /* AVSUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6798B271BA9C9D10020B47A /* AVSUploader.swift */; settings = {ASSET_TAGS = (); }; };
16 | B6798B2A1BA9E7BF0020B47A /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6798B291BA9E7BF0020B47A /* Config.swift */; settings = {ASSET_TAGS = (); }; };
17 | B6798B321BAA3FE90020B47A /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B6798B311BAA3FE90020B47A /* libz.tbd */; };
18 | B6798B341BAA411B0020B47A /* GCDWebServers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6798B331BAA411B0020B47A /* GCDWebServers.framework */; };
19 | B6798B351BAA411B0020B47A /* GCDWebServers.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B6798B331BAA411B0020B47A /* GCDWebServers.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | B6798B381BAA471C0020B47A /* SimpleWebServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6798B371BAA471C0020B47A /* SimpleWebServer.swift */; settings = {ASSET_TAGS = (); }; };
21 | B6798B3A1BAA4B200020B47A /* login.html in Resources */ = {isa = PBXBuildFile; fileRef = B6798B391BAA4B200020B47A /* login.html */; settings = {ASSET_TAGS = (); }; };
22 | B6EFD2821BA473CF009154C4 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6EFD2811BA473CF009154C4 /* CoreAudio.framework */; };
23 | B6EFD2841BA473E8009154C4 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6EFD2831BA473E8009154C4 /* AudioToolbox.framework */; };
24 | B6EFD2871BA5BAB9009154C4 /* SimplePCMRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EFD2861BA5BAB9009154C4 /* SimplePCMRecorder.swift */; settings = {ASSET_TAGS = (); }; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXCopyFilesBuildPhase section */
28 | B6798B361BAA411B0020B47A /* Embed Frameworks */ = {
29 | isa = PBXCopyFilesBuildPhase;
30 | buildActionMask = 2147483647;
31 | dstPath = "";
32 | dstSubfolderSpec = 10;
33 | files = (
34 | B6798B351BAA411B0020B47A /* GCDWebServers.framework in Embed Frameworks */,
35 | );
36 | name = "Embed Frameworks";
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXCopyFilesBuildPhase section */
40 |
41 | /* Begin PBXFileReference section */
42 | B6017A701BA46E4E006DABB5 /* AVSExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AVSExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
43 | B6017A741BA46E4E006DABB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | B6017A751BA46E4E006DABB5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
45 | B6017A771BA46E4E006DABB5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
46 | B6017A791BA46E4E006DABB5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
47 | B6017A7C1BA46E4E006DABB5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
48 | B6798B251BA9C6DB0020B47A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
49 | B6798B271BA9C9D10020B47A /* AVSUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AVSUploader.swift; sourceTree = ""; };
50 | B6798B291BA9E7BF0020B47A /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; };
51 | B6798B311BAA3FE90020B47A /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
52 | B6798B331BAA411B0020B47A /* GCDWebServers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GCDWebServers.framework; path = Carthage/Build/Mac/GCDWebServers.framework; sourceTree = ""; };
53 | B6798B371BAA471C0020B47A /* SimpleWebServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleWebServer.swift; sourceTree = ""; };
54 | B6798B391BAA4B200020B47A /* login.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = login.html; sourceTree = ""; };
55 | B6EFD2811BA473CF009154C4 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
56 | B6EFD2831BA473E8009154C4 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
57 | B6EFD2861BA5BAB9009154C4 /* SimplePCMRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimplePCMRecorder.swift; sourceTree = ""; };
58 | /* End PBXFileReference section */
59 |
60 | /* Begin PBXFrameworksBuildPhase section */
61 | B6017A6D1BA46E4E006DABB5 /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | B6798B321BAA3FE90020B47A /* libz.tbd in Frameworks */,
66 | B6798B261BA9C6DB0020B47A /* AVFoundation.framework in Frameworks */,
67 | B6EFD2841BA473E8009154C4 /* AudioToolbox.framework in Frameworks */,
68 | B6798B341BAA411B0020B47A /* GCDWebServers.framework in Frameworks */,
69 | B6EFD2821BA473CF009154C4 /* CoreAudio.framework in Frameworks */,
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | B6017A671BA46E4E006DABB5 = {
77 | isa = PBXGroup;
78 | children = (
79 | B6017A721BA46E4E006DABB5 /* AVSExample */,
80 | B6EFD2851BA473F1009154C4 /* Frameworks */,
81 | B6017A711BA46E4E006DABB5 /* Products */,
82 | );
83 | sourceTree = "";
84 | };
85 | B6017A711BA46E4E006DABB5 /* Products */ = {
86 | isa = PBXGroup;
87 | children = (
88 | B6017A701BA46E4E006DABB5 /* AVSExample.app */,
89 | );
90 | name = Products;
91 | sourceTree = "";
92 | };
93 | B6017A721BA46E4E006DABB5 /* AVSExample */ = {
94 | isa = PBXGroup;
95 | children = (
96 | B6798B291BA9E7BF0020B47A /* Config.swift */,
97 | B6017A751BA46E4E006DABB5 /* AppDelegate.swift */,
98 | B6798B371BAA471C0020B47A /* SimpleWebServer.swift */,
99 | B6EFD2861BA5BAB9009154C4 /* SimplePCMRecorder.swift */,
100 | B6798B271BA9C9D10020B47A /* AVSUploader.swift */,
101 | B6017A771BA46E4E006DABB5 /* ViewController.swift */,
102 | B6017A791BA46E4E006DABB5 /* Images.xcassets */,
103 | B6017A7B1BA46E4E006DABB5 /* Main.storyboard */,
104 | B6017A731BA46E4E006DABB5 /* Supporting Files */,
105 | );
106 | path = AVSExample;
107 | sourceTree = "";
108 | };
109 | B6017A731BA46E4E006DABB5 /* Supporting Files */ = {
110 | isa = PBXGroup;
111 | children = (
112 | B6798B391BAA4B200020B47A /* login.html */,
113 | B6017A741BA46E4E006DABB5 /* Info.plist */,
114 | );
115 | name = "Supporting Files";
116 | sourceTree = "";
117 | };
118 | B6EFD2851BA473F1009154C4 /* Frameworks */ = {
119 | isa = PBXGroup;
120 | children = (
121 | B6798B311BAA3FE90020B47A /* libz.tbd */,
122 | B6798B331BAA411B0020B47A /* GCDWebServers.framework */,
123 | B6798B251BA9C6DB0020B47A /* AVFoundation.framework */,
124 | B6EFD2831BA473E8009154C4 /* AudioToolbox.framework */,
125 | B6EFD2811BA473CF009154C4 /* CoreAudio.framework */,
126 | );
127 | name = Frameworks;
128 | sourceTree = "";
129 | };
130 | /* End PBXGroup section */
131 |
132 | /* Begin PBXNativeTarget section */
133 | B6017A6F1BA46E4E006DABB5 /* AVSExample */ = {
134 | isa = PBXNativeTarget;
135 | buildConfigurationList = B6017A8C1BA46E4E006DABB5 /* Build configuration list for PBXNativeTarget "AVSExample" */;
136 | buildPhases = (
137 | B6017A6C1BA46E4E006DABB5 /* Sources */,
138 | B6017A6D1BA46E4E006DABB5 /* Frameworks */,
139 | B6017A6E1BA46E4E006DABB5 /* Resources */,
140 | B6798B361BAA411B0020B47A /* Embed Frameworks */,
141 | );
142 | buildRules = (
143 | );
144 | dependencies = (
145 | );
146 | name = AVSExample;
147 | productName = AVSExample;
148 | productReference = B6017A701BA46E4E006DABB5 /* AVSExample.app */;
149 | productType = "com.apple.product-type.application";
150 | };
151 | /* End PBXNativeTarget section */
152 |
153 | /* Begin PBXProject section */
154 | B6017A681BA46E4E006DABB5 /* Project object */ = {
155 | isa = PBXProject;
156 | attributes = {
157 | LastSwiftUpdateCheck = 0700;
158 | LastUpgradeCheck = 0700;
159 | ORGANIZATIONNAME = TEst;
160 | TargetAttributes = {
161 | B6017A6F1BA46E4E006DABB5 = {
162 | CreatedOnToolsVersion = 6.4;
163 | };
164 | };
165 | };
166 | buildConfigurationList = B6017A6B1BA46E4E006DABB5 /* Build configuration list for PBXProject "AVSExample" */;
167 | compatibilityVersion = "Xcode 3.2";
168 | developmentRegion = English;
169 | hasScannedForEncodings = 0;
170 | knownRegions = (
171 | en,
172 | Base,
173 | );
174 | mainGroup = B6017A671BA46E4E006DABB5;
175 | productRefGroup = B6017A711BA46E4E006DABB5 /* Products */;
176 | projectDirPath = "";
177 | projectRoot = "";
178 | targets = (
179 | B6017A6F1BA46E4E006DABB5 /* AVSExample */,
180 | );
181 | };
182 | /* End PBXProject section */
183 |
184 | /* Begin PBXResourcesBuildPhase section */
185 | B6017A6E1BA46E4E006DABB5 /* Resources */ = {
186 | isa = PBXResourcesBuildPhase;
187 | buildActionMask = 2147483647;
188 | files = (
189 | B6017A7A1BA46E4E006DABB5 /* Images.xcassets in Resources */,
190 | B6017A7D1BA46E4E006DABB5 /* Main.storyboard in Resources */,
191 | B6798B3A1BAA4B200020B47A /* login.html in Resources */,
192 | );
193 | runOnlyForDeploymentPostprocessing = 0;
194 | };
195 | /* End PBXResourcesBuildPhase section */
196 |
197 | /* Begin PBXSourcesBuildPhase section */
198 | B6017A6C1BA46E4E006DABB5 /* Sources */ = {
199 | isa = PBXSourcesBuildPhase;
200 | buildActionMask = 2147483647;
201 | files = (
202 | B6017A781BA46E4E006DABB5 /* ViewController.swift in Sources */,
203 | B6798B381BAA471C0020B47A /* SimpleWebServer.swift in Sources */,
204 | B6017A761BA46E4E006DABB5 /* AppDelegate.swift in Sources */,
205 | B6798B2A1BA9E7BF0020B47A /* Config.swift in Sources */,
206 | B6798B281BA9C9D10020B47A /* AVSUploader.swift in Sources */,
207 | B6EFD2871BA5BAB9009154C4 /* SimplePCMRecorder.swift in Sources */,
208 | );
209 | runOnlyForDeploymentPostprocessing = 0;
210 | };
211 | /* End PBXSourcesBuildPhase section */
212 |
213 | /* Begin PBXVariantGroup section */
214 | B6017A7B1BA46E4E006DABB5 /* Main.storyboard */ = {
215 | isa = PBXVariantGroup;
216 | children = (
217 | B6017A7C1BA46E4E006DABB5 /* Base */,
218 | );
219 | name = Main.storyboard;
220 | sourceTree = "";
221 | };
222 | /* End PBXVariantGroup section */
223 |
224 | /* Begin XCBuildConfiguration section */
225 | B6017A8A1BA46E4E006DABB5 /* Debug */ = {
226 | isa = XCBuildConfiguration;
227 | buildSettings = {
228 | ALWAYS_SEARCH_USER_PATHS = NO;
229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
230 | CLANG_CXX_LIBRARY = "libc++";
231 | CLANG_ENABLE_MODULES = YES;
232 | CLANG_ENABLE_OBJC_ARC = YES;
233 | CLANG_WARN_BOOL_CONVERSION = YES;
234 | CLANG_WARN_CONSTANT_CONVERSION = YES;
235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
236 | CLANG_WARN_EMPTY_BODY = YES;
237 | CLANG_WARN_ENUM_CONVERSION = YES;
238 | CLANG_WARN_INT_CONVERSION = YES;
239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
240 | CLANG_WARN_UNREACHABLE_CODE = YES;
241 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
242 | CODE_SIGN_IDENTITY = "-";
243 | COPY_PHASE_STRIP = NO;
244 | DEBUG_INFORMATION_FORMAT = dwarf;
245 | ENABLE_STRICT_OBJC_MSGSEND = YES;
246 | ENABLE_TESTABILITY = YES;
247 | GCC_C_LANGUAGE_STANDARD = gnu99;
248 | GCC_DYNAMIC_NO_PIC = NO;
249 | GCC_NO_COMMON_BLOCKS = YES;
250 | GCC_OPTIMIZATION_LEVEL = 0;
251 | GCC_PREPROCESSOR_DEFINITIONS = (
252 | "DEBUG=1",
253 | "$(inherited)",
254 | );
255 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
258 | GCC_WARN_UNDECLARED_SELECTOR = YES;
259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
260 | GCC_WARN_UNUSED_FUNCTION = YES;
261 | GCC_WARN_UNUSED_VARIABLE = YES;
262 | MACOSX_DEPLOYMENT_TARGET = 10.10;
263 | MTL_ENABLE_DEBUG_INFO = YES;
264 | ONLY_ACTIVE_ARCH = YES;
265 | SDKROOT = macosx;
266 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
267 | };
268 | name = Debug;
269 | };
270 | B6017A8B1BA46E4E006DABB5 /* Release */ = {
271 | isa = XCBuildConfiguration;
272 | buildSettings = {
273 | ALWAYS_SEARCH_USER_PATHS = NO;
274 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
275 | CLANG_CXX_LIBRARY = "libc++";
276 | CLANG_ENABLE_MODULES = YES;
277 | CLANG_ENABLE_OBJC_ARC = YES;
278 | CLANG_WARN_BOOL_CONVERSION = YES;
279 | CLANG_WARN_CONSTANT_CONVERSION = YES;
280 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
281 | CLANG_WARN_EMPTY_BODY = YES;
282 | CLANG_WARN_ENUM_CONVERSION = YES;
283 | CLANG_WARN_INT_CONVERSION = YES;
284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
285 | CLANG_WARN_UNREACHABLE_CODE = YES;
286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
287 | CODE_SIGN_IDENTITY = "-";
288 | COPY_PHASE_STRIP = NO;
289 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
290 | ENABLE_NS_ASSERTIONS = NO;
291 | ENABLE_STRICT_OBJC_MSGSEND = YES;
292 | GCC_C_LANGUAGE_STANDARD = gnu99;
293 | GCC_NO_COMMON_BLOCKS = YES;
294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
296 | GCC_WARN_UNDECLARED_SELECTOR = YES;
297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
298 | GCC_WARN_UNUSED_FUNCTION = YES;
299 | GCC_WARN_UNUSED_VARIABLE = YES;
300 | MACOSX_DEPLOYMENT_TARGET = 10.10;
301 | MTL_ENABLE_DEBUG_INFO = NO;
302 | SDKROOT = macosx;
303 | };
304 | name = Release;
305 | };
306 | B6017A8D1BA46E4E006DABB5 /* Debug */ = {
307 | isa = XCBuildConfiguration;
308 | buildSettings = {
309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
310 | COMBINE_HIDPI_IMAGES = YES;
311 | FRAMEWORK_SEARCH_PATHS = (
312 | "$(inherited)",
313 | "$(PROJECT_DIR)",
314 | "$(PROJECT_DIR)/Carthage/Build/Mac",
315 | );
316 | INFOPLIST_FILE = AVSExample/Info.plist;
317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
318 | PRODUCT_BUNDLE_IDENTIFIER = "net.ioncannon.$(PRODUCT_NAME:rfc1034identifier)";
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | };
321 | name = Debug;
322 | };
323 | B6017A8E1BA46E4E006DABB5 /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
327 | COMBINE_HIDPI_IMAGES = YES;
328 | FRAMEWORK_SEARCH_PATHS = (
329 | "$(inherited)",
330 | "$(PROJECT_DIR)",
331 | "$(PROJECT_DIR)/Carthage/Build/Mac",
332 | );
333 | INFOPLIST_FILE = AVSExample/Info.plist;
334 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
335 | PRODUCT_BUNDLE_IDENTIFIER = "net.ioncannon.$(PRODUCT_NAME:rfc1034identifier)";
336 | PRODUCT_NAME = "$(TARGET_NAME)";
337 | };
338 | name = Release;
339 | };
340 | /* End XCBuildConfiguration section */
341 |
342 | /* Begin XCConfigurationList section */
343 | B6017A6B1BA46E4E006DABB5 /* Build configuration list for PBXProject "AVSExample" */ = {
344 | isa = XCConfigurationList;
345 | buildConfigurations = (
346 | B6017A8A1BA46E4E006DABB5 /* Debug */,
347 | B6017A8B1BA46E4E006DABB5 /* Release */,
348 | );
349 | defaultConfigurationIsVisible = 0;
350 | defaultConfigurationName = Release;
351 | };
352 | B6017A8C1BA46E4E006DABB5 /* Build configuration list for PBXNativeTarget "AVSExample" */ = {
353 | isa = XCConfigurationList;
354 | buildConfigurations = (
355 | B6017A8D1BA46E4E006DABB5 /* Debug */,
356 | B6017A8E1BA46E4E006DABB5 /* Release */,
357 | );
358 | defaultConfigurationIsVisible = 0;
359 | defaultConfigurationName = Release;
360 | };
361 | /* End XCConfigurationList section */
362 | };
363 | rootObject = B6017A681BA46E4E006DABB5 /* Project object */;
364 | }
365 |
--------------------------------------------------------------------------------