├── VERSION ├── .swift-version ├── Example ├── iOS Example │ ├── Resources │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Sounds │ │ │ ├── rain.mp3 │ │ │ ├── nature.mp3 │ │ │ ├── guitar-chord.wav │ │ │ ├── guitar-songs.mp3 │ │ │ ├── rain-thunder.ogg │ │ │ └── water-stream.wav │ │ └── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ ├── Supporting files │ │ └── Info.plist │ ├── SoundCell.swift │ ├── AppDelegate.swift │ └── ViewController.swift └── iOS Example.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ └── xcschemes │ │ └── iOS Example.xcscheme │ └── project.pbxproj ├── Soundable.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── Soundable iOS.xcscheme └── project.pbxproj ├── Soundable.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── CHANGELOG.md ├── Tests └── Info.plist ├── Source ├── Info.plist ├── Soundable.h ├── Playable.swift ├── SBError.swift ├── SoundsQueue.swift ├── Sound.swift └── Soundable.swift ├── LICENSE ├── Soundable.podspec ├── .gitignore └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/rain.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/rain.mp3 -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/nature.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/nature.mp3 -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/guitar-chord.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/guitar-chord.wav -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/guitar-songs.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/guitar-songs.mp3 -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/rain-thunder.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/rain-thunder.ogg -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Sounds/water-stream.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcardevnas/Soundable/HEAD/Example/iOS Example/Resources/Sounds/water-stream.wav -------------------------------------------------------------------------------- /Soundable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Soundable.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Soundable.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Soundable.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Soundable CHANGELOG 2 | 3 | ## 1.0.4 4 | 5 | * Added functionality to mute/unmute sounds and sound queues. 6 | 7 | ## 1.0.3 8 | 9 | * Code restructured to be more organized and compatible with Carthage. 10 | 11 | ## 1.0.2 12 | 13 | * Changed access control keyword for the `SoundsQueue` object initializer. 14 | 15 | ## 1.0.1 16 | 17 | * Changed access control keyword for the `Sound` object initializer. 18 | 19 | ## 1.0.0 20 | 21 | * Initial release. 22 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Luis Cardenas 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 | -------------------------------------------------------------------------------- /Soundable.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'Soundable' 4 | s.version = File.read('VERSION') 5 | s.summary = 'Playing sounds in your Swift applications and games never was that easy' 6 | 7 | s.description = <<-DESC 8 | Soundable is a tiny library that uses `AVFoundation` to manage the playing of sounds in iOS applications in a simple and easy way. You can play single audios, in sequence and in paralel, all is handled by the Soundable library and all they have completion closures when playing finishes. 9 | DESC 10 | 11 | s.homepage = 'https://github.com/thxou/Soundable' 12 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 13 | s.license = { :type => 'MIT', :file => 'LICENSE' } 14 | s.author = { 'thxou' => 'yo@thxou.com' } 15 | s.source = { :git => 'https://github.com/thxou/Soundable.git', :tag => s.version.to_s } 16 | s.social_media_url = 'https://twitter.com/thxou' 17 | 18 | s.ios.deployment_target = '9.0' 19 | s.platform = :ios, '9.0' 20 | s.requires_arc = true 21 | 22 | s.source_files = 'Source/*.swift' 23 | 24 | end 25 | -------------------------------------------------------------------------------- /Source/Soundable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Soundable.h 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | //! Project version number for Soundable. 28 | FOUNDATION_EXPORT double SoundableVersionNumber; 29 | 30 | //! Project version string for Soundable. 31 | FOUNDATION_EXPORT const unsigned char SoundableVersionString[]; 32 | 33 | -------------------------------------------------------------------------------- /Example/iOS Example/Supporting files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Soundable 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIBackgroundModes 24 | 25 | audio 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Source/Playable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Playable.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol Playable { 28 | 29 | var url: URL? { get set } 30 | var identifier: String { get set } 31 | var groupKey: String { get set } 32 | var loopsCount: Int { get set } 33 | var volume: Float { get set } 34 | var isMuted: Bool { get } 35 | 36 | func play(groupKey: String?, loopsCount: Int, completion: SoundCompletion?) 37 | func pause() 38 | func stop() 39 | 40 | func mute() 41 | func unmute() 42 | } 43 | -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift 3 | # Edit at https://www.gitignore.io/?templates=swift 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | # Package.resolved 46 | .build/ 47 | 48 | # CocoaPods 49 | # 50 | # We recommend against adding the Pods directory to your .gitignore. However 51 | # you should judge for yourself, the pros and cons are mentioned at: 52 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 53 | # 54 | # Pods/ 55 | # 56 | # Add this line if you want to avoid checking in source code from the Xcode workspace 57 | # *.xcworkspace 58 | 59 | # Carthage 60 | # 61 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 62 | # Carthage/Checkouts 63 | 64 | Carthage/Build 65 | 66 | # fastlane 67 | # 68 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 69 | # screenshots whenever they are needed. 70 | # For more information about the recommended setup visit: 71 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 72 | 73 | fastlane/report.xml 74 | fastlane/Preview.html 75 | fastlane/screenshots/**/*.png 76 | fastlane/test_output 77 | 78 | # Code Injection 79 | # 80 | # After new code Injection tools there's a generated folder /iOSInjectionProject 81 | # https://github.com/johnno1962/injectionforxcode 82 | 83 | iOSInjectionProject/ 84 | 85 | # End of https://www.gitignore.io/api/swift 86 | -------------------------------------------------------------------------------- /Source/SBError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SBError.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public enum SBError: Error { 28 | case playingFailed(reason: PlayingFailureReason) 29 | 30 | public enum PlayingFailureReason { 31 | case wrongUrl 32 | case noSoundsToPlay 33 | case audioDisabled 34 | } 35 | } 36 | 37 | 38 | // MARK: - Error Descriptions 39 | extension SBError: LocalizedError { 40 | public var errorDescription: String? { 41 | switch self { 42 | case .playingFailed(let reason): 43 | return reason.localizedDescription 44 | } 45 | } 46 | } 47 | 48 | extension SBError.PlayingFailureReason { 49 | var localizedDescription: String { 50 | switch self { 51 | case .wrongUrl: 52 | return NSLocalizedString("wrong_url", comment: "Player cannot play a sound with this url") 53 | case .noSoundsToPlay: 54 | return NSLocalizedString("no_sounds_to_play", comment: "The array does not contain any sound to play") 55 | case .audioDisabled: 56 | return NSLocalizedString("audio_disabled", comment: "The audio is disabled in Soundable. Set `Soundable.soundEnabled = true`") 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Example/iOS Example/SoundCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoundCell.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | import Soundable 27 | 28 | typealias PlayButtonClosure = () -> Void 29 | 30 | class SoundCell: UITableViewCell { 31 | 32 | @IBOutlet weak var titleLabel: UILabel? 33 | @IBOutlet weak var playButton: UIButton? 34 | @IBOutlet weak var pauseButton: UIButton? 35 | @IBOutlet weak var muteButton: UIButton? 36 | 37 | var playTappedClosure: PlayButtonClosure? 38 | 39 | var sound: Sound? 40 | 41 | 42 | func configureCell(with sound: Sound, isSelected: Bool, playTapped: @escaping PlayButtonClosure) { 43 | self.sound = sound 44 | 45 | playTappedClosure = playTapped 46 | 47 | titleLabel?.text = sound.name 48 | playButton?.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside) 49 | pauseButton?.addTarget(self, action: #selector(pauseButtonTapped), for: .touchUpInside) 50 | muteButton?.addTarget(self, action: #selector(muteButtonTapped), for: .touchUpInside) 51 | 52 | accessoryType = isSelected ? .checkmark : .none 53 | } 54 | 55 | @objc func playButtonTapped() { 56 | playTappedClosure?() 57 | } 58 | 59 | @objc func pauseButtonTapped() { 60 | sound?.pause() 61 | } 62 | 63 | @objc func muteButtonTapped() { 64 | if let isMuted = sound?.isMuted, isMuted { 65 | sound?.unmute() 66 | muteButton?.setTitle("Mute", for: .normal) 67 | } else { 68 | sound?.mute() 69 | muteButton?.setTitle("Unmute", for: .normal) 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Example/iOS Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | 27 | @UIApplicationMain 28 | class AppDelegate: UIResponder, UIApplicationDelegate { 29 | 30 | var window: UIWindow? 31 | 32 | 33 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 34 | // Override point for customization after application launch. 35 | return true 36 | } 37 | 38 | func applicationWillResignActive(_ application: UIApplication) { 39 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 40 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 41 | } 42 | 43 | func applicationDidEnterBackground(_ application: UIApplication) { 44 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | } 47 | 48 | func applicationWillEnterForeground(_ application: UIApplication) { 49 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 50 | } 51 | 52 | func applicationDidBecomeActive(_ application: UIApplication) { 53 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 54 | } 55 | 56 | func applicationWillTerminate(_ application: UIApplication) { 57 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /Example/iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Soundable.xcodeproj/xcshareddata/xcschemes/Soundable iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Example/iOS Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | import Soundable 27 | 28 | struct CellIdentifier { 29 | static let SoundCell = "SoundCell" 30 | } 31 | 32 | class ViewController: UIViewController { 33 | 34 | @IBOutlet weak var tableView: UITableView? 35 | @IBOutlet weak var soundNameField: UITextField? 36 | @IBOutlet weak var enableSoundButton: UIButton? 37 | 38 | var sounds: [Sound] = [] 39 | var selectedSounds: [Sound] = [] 40 | 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | setup() 46 | } 47 | 48 | fileprivate func setup() { 49 | // Activating session to play in background 50 | Soundable.activateSession(category: .playback) 51 | 52 | // Creating sounds to be played in sequence 53 | let sound1 = Sound(fileName: "guitar-chord.wav") 54 | let sound2 = Sound(fileName: "rain.mp3") 55 | let sound3 = Sound(fileName: "water-stream.wav") 56 | let sound4 = Sound(fileName: "rain-thunder.ogg") // Will not play this due to format 😈 57 | let sound5 = Sound(fileName: "nature.mp3") 58 | 59 | sounds = [sound1, sound2, sound3, sound4, sound5] 60 | 61 | // Gesture to dismiss keyboard 62 | let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) 63 | gesture.cancelsTouchesInView = false 64 | view.addGestureRecognizer(gesture) 65 | 66 | // Configuring enable/disable sounds button 67 | configureEnableButton() 68 | 69 | // Observing interruptions 70 | Soundable.observeInterruptions { (type, userInfo) in 71 | if type == .began { 72 | print("interruption began") 73 | } else if type == .ended { 74 | print("interruption ended") 75 | } 76 | } 77 | } 78 | 79 | 80 | // MARK: - Actions 81 | @IBAction func playSounds(_ sender: UIButton) { 82 | tableView?.isUserInteractionEnabled = false 83 | 84 | // Play sounds in sequence 85 | selectedSounds.play { error in 86 | if let error = error { 87 | print("error: \(error.localizedDescription)") 88 | } 89 | print("finished playing queue!") 90 | self.tableView?.isUserInteractionEnabled = true 91 | } 92 | } 93 | 94 | @IBAction func playSound(_ sender: UIButton) { 95 | // Playing the sound written in the text field 96 | guard let soundName = soundNameField?.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines), soundName != "" else { 97 | return 98 | } 99 | 100 | soundName.tryToPlay { error in 101 | if let error = error { 102 | print("error: \(error.localizedDescription)") 103 | } 104 | } 105 | } 106 | 107 | @IBAction func stopAllSounds(_ sender: UIButton) { 108 | Soundable.stopAll() 109 | 110 | tableView?.isUserInteractionEnabled = true 111 | } 112 | 113 | @IBAction func disableAudio(_ sender: UIButton) { 114 | Soundable.soundEnabled = !Soundable.soundEnabled 115 | 116 | configureEnableButton() 117 | } 118 | 119 | @objc func handleTap() { 120 | view.endEditing(true) 121 | } 122 | 123 | 124 | // MARK: - Helpers 125 | fileprivate func play(_ sound: Sound) { 126 | sound.play { error in 127 | if let error = error { 128 | print("error: \(error.localizedDescription)") 129 | } 130 | print("finished playing: \(sound.name ?? "")") 131 | } 132 | } 133 | 134 | fileprivate func configureEnableButton() { 135 | enableSoundButton?.setTitle("\(Soundable.soundEnabled ? "Disable" : "Enable") Soundable audio", for: .normal) 136 | } 137 | } 138 | 139 | 140 | extension ViewController : UITableViewDataSource { 141 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 142 | return sounds.count 143 | } 144 | 145 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 146 | let sound = sounds[indexPath.row] 147 | 148 | let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.SoundCell, for: indexPath) as! SoundCell 149 | cell.configureCell(with: sound, isSelected: selectedSounds.contains(sound), playTapped: { [unowned self] in 150 | self.play(sound) 151 | }) 152 | return cell 153 | } 154 | } 155 | 156 | 157 | extension ViewController : UITableViewDelegate { 158 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 159 | let sound = sounds[indexPath.row] 160 | let cell = tableView.cellForRow(at: indexPath) 161 | 162 | if let index = selectedSounds.firstIndex(of: sound) { 163 | selectedSounds.remove(at: index) 164 | cell?.accessoryType = .none 165 | } else { 166 | selectedSounds.append(sound) 167 | cell?.accessoryType = .checkmark 168 | } 169 | 170 | tableView.deselectRow(at: indexPath, animated: true) 171 | } 172 | } 173 | 174 | -------------------------------------------------------------------------------- /Source/SoundsQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoundsQueue.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import AVFoundation 26 | 27 | /// The associated key for the completion closure. 28 | fileprivate var associatedSoundsQueueCompletionKey = "kAssociatedSoundsQueueCompletionKey" 29 | 30 | extension Notification.Name { 31 | static let QueuePlayerPlayedToEnd = Notification.Name(rawValue: "QueuePlayerPlayedToEnd") 32 | } 33 | 34 | /// An object to encapsulate playing queues functionality. 35 | public class SoundsQueue : Playable { 36 | 37 | /// The queue player that will play the queued sounds. 38 | var queuePlayer: AVQueuePlayer? 39 | 40 | /// The group key where to play the sound queue. 41 | public var groupKey: String = SoundableKey.DefaultGroupKey 42 | 43 | /// The identifier for the sounds queue. 44 | public var identifier = "" 45 | 46 | /// The number of times the queue will be played. 47 | public var loopsCount = 0 48 | 49 | /// Not used in this object. 50 | public var url: URL? 51 | 52 | /// The array of `Sound` objects to be played. 53 | private var sounds: [Sound] = [] 54 | 55 | /// Keeps track of the remaining number of sounds to play. 56 | private var pendingNumberOfSoundsToPlay = 0 57 | 58 | /// Keeps track of the times the sounds queue has been played. 59 | private var numberOfLoopsPlayed = 0 60 | 61 | /// The volume of the sound queue items. 62 | public var volume: Float { 63 | get { return queuePlayer?.volume ?? 1.0 } 64 | set { queuePlayer?.volume = volume } 65 | } 66 | 67 | /// Indicates if the sound queue is currently muted. 68 | public var isMuted: Bool { 69 | return queuePlayer?.volume == 0.0 70 | } 71 | 72 | 73 | // MARK: - Initializers 74 | private init() { } 75 | 76 | /// Created a `SoundsQueue` object with the given sounds. 77 | /// 78 | /// - parameter sounds: An array of `Sound` object to play. 79 | public init(sounds: [Sound]) { 80 | self.sounds = sounds.filter({ $0.player != nil }) 81 | self.identifier = uniqueString() 82 | 83 | NotificationCenter.default.addObserver(self, 84 | selector: #selector(playerItemDidReachEnd), 85 | name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, 86 | object: nil) 87 | 88 | NotificationCenter.default.addObserver(self, 89 | selector: #selector(playerItemDidReachEnd), 90 | name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, 91 | object: nil) 92 | createPlayer() 93 | } 94 | 95 | 96 | // MARK: - Actions 97 | /// Funcion called after each sound has finished playing. 98 | @objc fileprivate func playerItemDidReachEnd() { 99 | pendingNumberOfSoundsToPlay -= 1 100 | if pendingNumberOfSoundsToPlay <= 0 { 101 | if numberOfLoopsPlayed >= loopsCount { 102 | numberOfLoopsPlayed = 0 103 | unmute() 104 | 105 | let completion = objc_getAssociatedObject(self, &associatedSoundsQueueCompletionKey) as? SoundCompletion 106 | completion?(nil) 107 | 108 | objc_setAssociatedObject(self, &associatedSoundsQueueCompletionKey, nil, .OBJC_ASSOCIATION_COPY_NONATOMIC) 109 | } 110 | else { 111 | numberOfLoopsPlayed += 1 112 | createPlayer() 113 | queuePlayer?.play() 114 | } 115 | } 116 | } 117 | 118 | 119 | // MARK: - Private 120 | private func setupItems() -> [AVPlayerItem] { 121 | var items: [AVPlayerItem] = [] 122 | for sound in sounds { 123 | if let url = sound.url { 124 | items.append(AVPlayerItem(url: url)) 125 | } 126 | } 127 | return items 128 | } 129 | 130 | private func createPlayer() { 131 | let items = setupItems() 132 | 133 | queuePlayer = AVQueuePlayer(items: items) 134 | pendingNumberOfSoundsToPlay = items.count 135 | } 136 | 137 | private func uniqueString() -> String { 138 | return String(UInt(bitPattern: ObjectIdentifier(self))) 139 | } 140 | 141 | } 142 | 143 | 144 | // MARK: - Playable 145 | extension SoundsQueue { 146 | /// Plays the current sounds queue. 147 | /// 148 | /// - parameter groupKey: The group where the sounds queue will be played. 149 | /// - parameter loopsCount: The number of times the sounds queue will be played before calling 150 | /// the completion closure. 151 | /// - parameter completion: The completion closure called after the sounds queue has 152 | /// finished playing. 153 | public func play(groupKey: String? = nil, loopsCount: Int = 0, completion: SoundCompletion? = nil) { 154 | if !Soundable.soundEnabled { 155 | completion?(SBError.playingFailed(reason: .audioDisabled)) 156 | return 157 | } 158 | 159 | self.groupKey = groupKey ?? SoundableKey.DefaultGroupKey 160 | self.loopsCount = loopsCount 161 | 162 | if let completion = completion { 163 | objc_setAssociatedObject(self, &associatedSoundsQueueCompletionKey, completion, .OBJC_ASSOCIATION_COPY_NONATOMIC) 164 | } 165 | 166 | queuePlayer?.play() 167 | 168 | Soundable.addPlayableItem(self) 169 | } 170 | 171 | /// Pauses the sounds queue. 172 | public func pause() { 173 | queuePlayer?.pause() 174 | } 175 | 176 | /// Stops the sounds queue. 177 | public func stop() { 178 | queuePlayer?.removeAllItems() 179 | unmute() 180 | 181 | Soundable.removePlayableItem(self) 182 | } 183 | 184 | /// Mutes the sounds queue. 185 | public func mute() { 186 | queuePlayer?.volume = 0.0 187 | } 188 | 189 | /// Unmutes the sounds queue. 190 | public func unmute() { 191 | if queuePlayer?.volume == 0.0 { 192 | queuePlayer?.volume = 1.0 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Source/Sound.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sound.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import AVFoundation 26 | 27 | /// The associated key for the completion closure. 28 | fileprivate var associatedSoundCompletionKey = "kAssociatedSoundCompletionKey" 29 | 30 | /// An object to encapsulate sound functionality. 31 | public class Sound : NSObject, Playable { 32 | 33 | /// The player that will play the sound. 34 | var player: AVAudioPlayer? 35 | 36 | /// The name of the sound (file name with extension). 37 | public var name: String? 38 | 39 | /// The group key where to play the sound. 40 | public var groupKey: String = SoundableKey.DefaultGroupKey 41 | 42 | /// The identifier for the sounds queue. 43 | public var identifier = "" 44 | 45 | /// The url where the audio file is located. 46 | public var url: URL? 47 | 48 | /// The number of times the sound will be played. 49 | public var loopsCount: Int { 50 | get { return player?.numberOfLoops ?? 0 } 51 | set { player?.numberOfLoops = loopsCount } 52 | } 53 | 54 | /// The volume of the sound. 55 | public var volume: Float { 56 | get { return player?.volume ?? 1.0 } 57 | set { player?.volume = volume } 58 | } 59 | 60 | /// Indicates if the sound is currently playing. 61 | public var isPlaying: Bool { 62 | return player?.isPlaying ?? false 63 | } 64 | 65 | /// Indicates if the sound is currently muted. 66 | public var isMuted: Bool { 67 | return player?.volume == 0.0 68 | } 69 | 70 | 71 | // MARK: - Initializers 72 | override private init() { } 73 | 74 | /// Creates a `Sound` object for the given audio file. 75 | /// 76 | /// - parameter fileName: The name of the file with its extension. 77 | /// - parameter bundle: The bundle where the audio file is located. By default it 78 | /// uses the main bundle. 79 | public init(fileName: String, bundle: Bundle = Bundle.main) { 80 | super.init() 81 | 82 | let urlName = URL(fileURLWithPath: fileName) 83 | let file = urlName.deletingPathExtension().lastPathComponent 84 | let fileExtension = urlName.pathExtension 85 | 86 | name = fileName 87 | if let path = bundle.path(forResource: file, ofType: fileExtension) { 88 | url = URL(fileURLWithPath: path) 89 | identifier = url?.absoluteString ?? "" 90 | } 91 | 92 | preparePlayer() 93 | } 94 | 95 | /// Creates a `Sound` object for the given url. 96 | /// 97 | /// - parameter url: The url of the audio file to be played. 98 | public init(url: URL) { 99 | super.init() 100 | 101 | self.url = url 102 | name = url.lastPathComponent 103 | identifier = url.absoluteString 104 | 105 | preparePlayer() 106 | } 107 | 108 | 109 | // MARK: - Private 110 | private func preparePlayer() { 111 | if identifier == "" { 112 | print("could not create an identifier for the sound") 113 | return 114 | } 115 | 116 | do { 117 | if let url = url { 118 | player = try AVAudioPlayer(contentsOf: url) 119 | player?.prepareToPlay() 120 | player?.delegate = self 121 | player?.volume = 1.0 122 | } else { 123 | print("missing audio url to play") 124 | } 125 | } 126 | catch let error { 127 | print("error creating player with provided url: \(error)") 128 | } 129 | } 130 | 131 | } 132 | 133 | 134 | // MARK: - Playable 135 | extension Sound { 136 | // MARK: - Playing sounds 137 | /// Plays the current sound. 138 | /// 139 | /// - parameter groupKey: The group where the sound will be played. 140 | /// - parameter loopsCount: The number of times the sound will be played before calling 141 | /// the completion closure. 142 | /// - parameter completion: The completion closure called after the sound has finished playing. 143 | public func play(groupKey: String? = nil, loopsCount: Int = 0, completion: SoundCompletion? = nil) { 144 | if player == nil { 145 | completion?(SBError.playingFailed(reason: .wrongUrl)) 146 | return 147 | } 148 | 149 | if !Soundable.soundEnabled { 150 | completion?(SBError.playingFailed(reason: .audioDisabled)) 151 | return 152 | } 153 | 154 | self.groupKey = groupKey ?? SoundableKey.DefaultGroupKey 155 | 156 | if let completion = completion { 157 | objc_setAssociatedObject(self, &associatedSoundCompletionKey, completion, .OBJC_ASSOCIATION_COPY_NONATOMIC) 158 | } 159 | 160 | player?.numberOfLoops = loopsCount 161 | player?.play() 162 | 163 | Soundable.addPlayableItem(self) 164 | } 165 | 166 | /// Pauses the sound. 167 | public func pause() { 168 | player?.pause() 169 | } 170 | 171 | /// Stops the sound. 172 | public func stop() { 173 | player?.stop() 174 | unmute() 175 | 176 | Soundable.removePlayableItem(self) 177 | } 178 | 179 | /// Mute the sound. 180 | public func mute() { 181 | player?.volume = 0.0 182 | } 183 | 184 | /// Unmute the sound. 185 | public func unmute() { 186 | if player?.volume == 0.0 { 187 | player?.volume = 1.0 188 | } 189 | } 190 | } 191 | 192 | 193 | extension Sound : AVAudioPlayerDelegate { 194 | public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { 195 | let completion = objc_getAssociatedObject(self, &associatedSoundCompletionKey) as? SoundCompletion 196 | completion?(nil) 197 | 198 | objc_setAssociatedObject(self, &associatedSoundCompletionKey, nil, .OBJC_ASSOCIATION_COPY_NONATOMIC) 199 | 200 | Soundable.removePlayableItem(self) 201 | unmute() 202 | } 203 | 204 | public func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) { 205 | let completion = objc_getAssociatedObject(self, &associatedSoundCompletionKey) as? SoundCompletion 206 | completion?(error) 207 | 208 | objc_setAssociatedObject(self, &associatedSoundCompletionKey, nil, .OBJC_ASSOCIATION_COPY_NONATOMIC) 209 | 210 | Soundable.removePlayableItem(self) 211 | unmute() 212 | } 213 | } 214 | 215 | 216 | extension String { 217 | /// Tries to play the audio file by the fileName given in the `String`. 218 | /// 219 | /// - parameter completion: The completion closure called after the sound has finished playing 220 | /// or an error if any. 221 | public func tryToPlay(_ completion: SoundCompletion? = nil) { 222 | let sound = Sound(fileName: self) 223 | sound.play { error in 224 | completion?(error) 225 | } 226 | } 227 | } 228 | 229 | 230 | extension URL { 231 | /// Tries to play the audio file by the url given. 232 | /// 233 | /// - parameter completion: The completion closure called after the sound has finished playing 234 | /// or an error if any. 235 | public func tryToPlay(_ completion: SoundCompletion? = nil) { 236 | let sound = Sound(url: self) 237 | sound.play { error in 238 | completion?(error) 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ============== 3 | 4 | [![Pod Version](http://img.shields.io/cocoapods/v/Soundable.svg?style=flat)](https://github.com/ThXou/Soundable) 5 | [![Pod Platform](http://img.shields.io/cocoapods/p/Soundable.svg?style=flat)](https://github.com/ThXou/Soundable) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![Pod License](http://img.shields.io/cocoapods/l/Soundable.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) 8 | 9 | 10 | Soundable is a tiny library that uses `AVFoundation` to manage the playing of sounds in iOS applications in a simple and easy way. You can play single audios, in sequence and in parallel, all is handled by the Soundable library and all they have completion closures when playing finishes. 11 | 12 | - [Requirements](#requirements) 13 | - [Install](#install) 14 | - [Setup](#setup) 15 | - [Playing Sounds](#playing-sounds) 16 | - [Single Sounds](#single-sounds) 17 | - [Multiple Sounds](#multiple-sounds) 18 | - [Stop Sounds](#stop-sounds) 19 | - [Mute Sounds](#mute-sounds) 20 | - [Looped Sounds](#looped-sounds) 21 | - [Disabling Sounds](#disabling-sounds) 22 | - [Setup Audio Session Category](#setup-audio-session-category) 23 | - [Handling Audio Interruptions](#handling-audio-interruptions) 24 | - [Credits](#credits) 25 | 26 | ## Requirements 27 | 28 | * iOS 9.0+ 29 | * Xcode 10.0+ 30 | * Swift 4.2+ 31 | 32 | 33 | ## Install 34 | 35 | ### Cocoapods 36 | 37 | Add this line to your `Podfile`: 38 | 39 | ```ruby 40 | pod 'Soundable', '~> 1.0' 41 | ``` 42 | 43 | ### Carthage 44 | 45 | Add this line to your `cartfile`: 46 | 47 | ```ruby 48 | github "ThXou/Soundable" ~> 1.0 49 | ``` 50 | 51 | And then follow the official documentation about [Adding frameworks to an application](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). 52 | 53 | ## Setup 54 | 55 | Import `Soundable` in your source file: 56 | 57 | ```swift 58 | import Soundable 59 | ``` 60 | 61 | ## Playing Sounds 62 | 63 | ### Single Sounds 64 | 65 | `Soundable` provides multiple ways to play a sound. The first one is by creating a `Sound` object: 66 | 67 | ```swift 68 | let sound = Sound(fileName: "guitar-chord.wav") 69 | sound.play() 70 | ``` 71 | 72 | This will play the `guitar-chord.wav` track located in the main bundle of the application. If you have multiple bundles in your application, use the `fileName:bundle:` function to create the sound. If you have an `URL` object instead, you can use: 73 | 74 | ```swift 75 | let sound = Sound(url: url) 76 | sound.play() 77 | ``` 78 | 79 | The second one is using the `Soundable` class functions: 80 | 81 | ```swift 82 | Soundable.play(fileName: "guitar-chord.wav") 83 | ``` 84 | And the third is for the laziest people. Put the file name in a `String` object and just `tryToPlay`: 85 | 86 | ```swift 87 | "guitar-chord.wav".tryToPlay() 88 | ``` 89 | 90 | This is possible due to a simple `String` category packed with the library that will try to play an audio file located in the application's main bundle with the specified name. 91 | 92 | If you have an `URL` object and are lazzy too, you can use it like this also: 93 | 94 | ```swift 95 | url.tryToPlay() 96 | ``` 97 | 98 | All these functions have their respective completion closures that passes an `Error` object if something wrong have happened in the process: 99 | 100 | ```swift 101 | sound.play { error in 102 | if let error = error { 103 | print("error: \(error.localizedDescription)") 104 | } 105 | } 106 | ``` 107 | 108 | ### Multiple Sounds 109 | 110 | #### Playing In Parallel 111 | 112 | To play audios in parallel your only have to worry on call the `play` function in all the audios you want to play in parallel, all the completion closures will be called when the audio finished playing. 113 | 114 | #### Playing In Sequence 115 | 116 | `Soundable` supports the playing of audios in sequence, and as for a single sound, you have multitple ways to play audios in sequence. The first one is the best (IMO): 117 | 118 | ```swift 119 | let sound1 = Sound(fileName: "guitar-chord.wav") 120 | let sound2 = Sound(fileName: "rain.mp3") 121 | let sound3 = Sound(fileName: "water-stream.wav") 122 | 123 | let sounds = [sound1, sound2, sound3] 124 | sounds.play() 125 | ``` 126 | 127 | Or: 128 | 129 | ```swift 130 | [sound1, sound2, sound3].play() 131 | ``` 132 | 133 | Can you play and array of `Sound` objects?. Yes. This is thanks to a simple `Sequence` extension packed with the library that only accepts `Sound` objects to play. 134 | 135 | The second one is using the `Soundable` class functions, again: 136 | 137 | ```swift 138 | Soundable.play(sounds: [sound1, sound2, sound3]) 139 | ``` 140 | 141 | And the third is using the `SoundsQueue` object to create a queue of sounds: 142 | 143 | ```swift 144 | let soundsQueue = SoundsQueue(sounds: [sound1, sound2, sound3]) 145 | soundsQueue.play() 146 | ``` 147 | 148 | As for single sounds, you also have the completion closure after all the sound sequence have been played. 149 | 150 | ## Stop Sounds 151 | 152 | To stop sounds and queues is as simple as play them. If you created the sound using the `Sound` object do it like this: 153 | 154 | ```swift 155 | let sound = Sound(fileName: "guitar-chord.wav") 156 | sound.play() 157 | ... 158 | sound.stop() 159 | ``` 160 | 161 | You can stop a specific sound using the `Soundable` class functions: 162 | 163 | ```swift 164 | Soundable.stop(sound) 165 | ``` 166 | 167 | You can stop all the sounds currently playing with `Soundable`, including sound queues: 168 | 169 | ```swift 170 | Soundable.stopAll() 171 | ``` 172 | 173 | Or you can stop all the sounds in a specific group. I explain to you what is that thing of "Groups" in the next section. 174 | 175 | > **Stop the sounds or sound queues does not trigger the completion closure.** 176 | 177 | 178 | ## Sound Groups 179 | 180 | Sound groups is a feature that allows you to group sounds under the same string key, then you can stop all the sounds in that group and keep playing the rest. 181 | 182 | By default all the sounds and sound queues are created under the `SoundableKey.DefaultGroupKey` key. In this way, you can group for example, game sounds under the *"game_sounds"* key and then stop only those sounds: 183 | 184 | ```swift 185 | Soundable.stopAll(for: "game_sounds") 186 | ``` 187 | 188 | All the rest of the sounds keep playing until they reach the end of the track or queue. 189 | 190 | You can set the group where a sound will belong to in the `groupKey` parameter of every `play` function. For example, when creating a `Sound` object: 191 | 192 | ```swift 193 | let sound = Sound(fileName: "sprite-walk.wav") 194 | sound.play(groupKey: "game_sounds") { error in 195 | // Handle error if any 196 | } 197 | ``` 198 | 199 | ## Mute sounds 200 | 201 | If you don't want to completelly stop the sound but only mute all sounds that are playing (Aka put the volume to 0.0), then use the `Soundable` mute functions: 202 | 203 | ```swift 204 | // To mute 205 | Soundable.muteAll() 206 | Soundable.muteAll(for: "game_sounds") 207 | 208 | // To unmute 209 | Soundable.unmuteAll() 210 | Soundable.unmuteAll(for: "game_sounds") 211 | ``` 212 | 213 | Alternativelly you can mute/unmute single sounds and queues: 214 | 215 | ```swift 216 | sound.mute() 217 | sound.unmute() 218 | 219 | soundsQueue.mute() 220 | soundsQueue.unmute() 221 | ``` 222 | 223 | Even check for the muting state with the sound and queue's `isMuted` property. 224 | 225 | If the sound or queue finished while muted, the completion closure is called anyway and the mute state of the sound and queue is restored (Aka volume turns to be zero again). 226 | 227 | ## Looped Sounds 228 | 229 | Play sounds and sound queues in loop by setting the `loopsCount` parameter in every `play` call, as with the `groupKey`: 230 | 231 | ```swift 232 | let sound = Sound(fileName: "sprite-walk.wav") 233 | sound.play(groupKey: "game_sounds", loopsCount: 2) { error in 234 | // Handle error if any 235 | } 236 | ``` 237 | 238 | The sound or sound queue will play a total of `loopsCount + 1` times before it triggers the completion closure. 239 | 240 | ## Disabling Sounds 241 | 242 | You can enable/disable all the currently playing sounds and sound queues by setting the `soundEnabled` property of the `Soundable` class: 243 | 244 | ```swift 245 | Soundable.soundEnabled = false 246 | ``` 247 | 248 | If disabled, it will stop all the playing sounds and sound queues and will return an error for subsequent attempts of playing sounds with the library. Disabling the sound system will not fire the completion closures. 249 | 250 | ## Setup Audio Session Category 251 | 252 | You can setup and activate the shared `AVAudioSession` category with a single call (no error handler). For example to continue playing the sounds when in the app is in background or the device is locked (background modes required): 253 | 254 | ```swift 255 | import AVFoundation 256 | 257 | // Setup the category 258 | Soundable.activateSession(category: .playback) 259 | ``` 260 | 261 | Or also deactivate the current active session category: 262 | 263 | ```swift 264 | Soundable.deactivateSession() 265 | ``` 266 | 267 | ## Handling Audio Interruptions 268 | 269 | A playing sound or sound queue can be interrupted due to many reasons. For example when you are playing your sounds and then the user receives a phone call, the operating system stops all the sounds playing arround in order to listen the incoming audio from the call. In this case, `Soundable` allows you to catch this kind of events and let you react to an audio interruption: 270 | 271 | ```swift 272 | Soundable.observeInterruptions { (type, userInfo) in 273 | if type == .began { 274 | print("interruption began") 275 | } else if type == .ended { 276 | print("interruption ended") 277 | } 278 | } 279 | ``` 280 | 281 | In the closure you will receive the type of interruption, whether if it has `.began` or `.ended`, and the `userInfo` object containing the details about the interruption in order to make a more finest handling. 282 | 283 | ## Credits 284 | 285 | The sounds in the code example has been downloaded from the FreeSound database ([https://freesound.org](https://freesound.org/)). -------------------------------------------------------------------------------- /Source/Soundable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Soundable.swift 3 | // 4 | // Copyright © 2018 Luis Cardenas. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import AVFoundation 26 | 27 | /// A closure called after playing a sound or sound queue. 28 | public typealias SoundCompletion = (_ error: Error?) -> Void 29 | 30 | /// A closure called when an interruption has occurred. 31 | public typealias AVAudioSessionInterruptionClosure = (_ type: AVAudioSession.InterruptionType, _ userInfo: [AnyHashable : Any]?) -> Void 32 | 33 | /// Alias for the `AVAudioSession.Category` type. 34 | public typealias SessionCategory = AVAudioSession.Category 35 | 36 | /// The associated key for the interruption closure. 37 | fileprivate var associatedSoundInterruptionCompletionKey = "kAssociatedSoundInterruptionCompletionKey" 38 | 39 | /// Static keys used in the library. 40 | public struct SoundableKey { 41 | public static let SoundEnabled = "kSoundableSoundEnabled" 42 | public static let DefaultGroupKey = "kSoundableDefaultGroupKey" 43 | } 44 | 45 | /// `Soundable` is a class that acts as a manager of all the playing sounds. 46 | public class Soundable { 47 | 48 | private static var shared = Soundable() 49 | 50 | /// An array of `Playable` objects to track the playing sounds and queues. 51 | private var playingSounds: [String: Playable] = [:] 52 | 53 | /// The apps audio shared session. 54 | private var audioSession = AVAudioSession.sharedInstance() 55 | 56 | /// The readonly category of the current audio session. 57 | public static var sessionCategory: SessionCategory { 58 | get { return shared.audioSession.category } 59 | } 60 | 61 | /// Enables/disables the sounds played using `Soundable` functions. 62 | public static var soundEnabled: Bool = { 63 | guard let value = UserDefaults.standard.string(forKey: SoundableKey.SoundEnabled) else { 64 | return true 65 | } 66 | return value == "true" 67 | }() { didSet { 68 | UserDefaults.standard.set(soundEnabled ? "true" : "false", forKey: SoundableKey.SoundEnabled) 69 | if !soundEnabled { 70 | stopAll() 71 | } 72 | } 73 | } 74 | 75 | // MARK: - Audio Session 76 | /// Sets and activate a new category for the audio session. 77 | /// 78 | /// - parameter category: The new category to set and activate for the audio session. 79 | public class func activateSession(category: SessionCategory, options: AVAudioSession.CategoryOptions = []) { 80 | if !shared.audioSession.availableCategories.contains(category) { 81 | fatalError("error: The '\(category)' category is not available for this device") 82 | } 83 | 84 | do { 85 | if #available(iOS 10.0, *) { 86 | try shared.audioSession.setCategory(category, mode: .default, options: options) 87 | } else { 88 | shared.audioSession.perform(NSSelectorFromString("setCategory:withOptions:error:"), with: category, with: options) 89 | } 90 | try shared.audioSession.setActive(true) 91 | } 92 | catch let error { 93 | print("error activating session with category \(category): \(error.localizedDescription)") 94 | } 95 | } 96 | 97 | /// Deactivates the current audio session. 98 | public class func deactivateSession() { 99 | do { try shared.audioSession.setActive(false) } 100 | catch let error { 101 | print("error deactivating session \(error.localizedDescription)") 102 | } 103 | } 104 | 105 | /// Setup an observer for audio interruptions in the playing session. 106 | /// 107 | /// Interruptions occur when other applications start playing audio, make calls or similar, requiring your 108 | /// app to stop the audio currently playing. 109 | /// 110 | /// - parameter interruptionClosure: The interruption closure to be called when an interruption has ocurred 111 | /// in the session. It returns the parameter `type`, which indicates if 112 | /// the interruption has `began` or `ended`, and the parameter `userInfo` 113 | /// which contains the notification's userInfo for further handling. 114 | public class func observeInterruptions(_ interruptionClosure: @escaping AVAudioSessionInterruptionClosure) { 115 | objc_setAssociatedObject(self, &associatedSoundInterruptionCompletionKey, interruptionClosure, .OBJC_ASSOCIATION_COPY_NONATOMIC) 116 | 117 | NotificationCenter.default.addObserver(Soundable.shared, 118 | selector: #selector(handleInterruption), 119 | name: AVAudioSession.interruptionNotification, 120 | object: nil) 121 | } 122 | 123 | 124 | // MARK: - Playing sounds 125 | /// Plays the given audio file. 126 | /// 127 | /// - parameter fileName: The name of the file with its extension. 128 | /// - parameter groupKey: The group where the audio will be played. 129 | /// - parameter loopsCount: The number of times the sound will be played before calling 130 | /// the completion closure. 131 | /// - parameter completion: The completion closure called after the audio has finished playing. 132 | public class func play(fileName: String, groupKey: String = SoundableKey.DefaultGroupKey, loopsCount: Int = 0, completion: SoundCompletion? = nil) { 133 | let sound = Sound(fileName: fileName) 134 | sound.groupKey = groupKey 135 | sound.loopsCount = loopsCount 136 | play(sound, completion: completion) 137 | } 138 | 139 | /// Plays the given sound. 140 | /// 141 | /// - parameter sound: The sound object to be played. 142 | /// - parameter completion: The completion closure called after the audio has finished playing. 143 | public class func play(_ sound: Sound, completion: SoundCompletion? = nil) { 144 | playItem(sound, completion: completion) 145 | } 146 | 147 | 148 | // MARK: - Playing sounds queues 149 | /// Plays the given sounds in sequence. 150 | /// 151 | /// - parameter sounds: An array of `Sound` objects to be played in sequence. 152 | /// - parameter groupKey: The group where the sounds will be played. 153 | /// - parameter loopsCount: The number of times the sounds will be played before calling 154 | /// the completion closure. 155 | /// - parameter completion: The completion closure called after the sounds has finished playing. 156 | public class func play(sounds: [Sound], groupKey: String = SoundableKey.DefaultGroupKey, loopsCount: Int = 0, completion: SoundCompletion? = nil) { 157 | let soundsQueue = SoundsQueue(sounds: sounds) 158 | soundsQueue.groupKey = groupKey 159 | soundsQueue.loopsCount = loopsCount 160 | playQueue(soundsQueue, completion: completion) 161 | } 162 | 163 | /// Plays the given sound queue. 164 | /// 165 | /// - parameter soundsQueue: The sound queue object to be played. 166 | /// - parameter completion: The completion closure called after the sound queue has 167 | /// finished playing. 168 | public class func playQueue(_ soundsQueue: SoundsQueue, completion: SoundCompletion? = nil) { 169 | playItem(soundsQueue, completion: completion) 170 | } 171 | 172 | 173 | // MARK: - Stop sounds 174 | /// Stops the given `Sound` object. 175 | public class func stop(_ sound: Sound) { 176 | sound.stop() 177 | } 178 | 179 | /// Stops the given `SoundsQueue` object. 180 | public class func stopQueue(_ soundsQueue: SoundsQueue) { 181 | soundsQueue.stop() 182 | } 183 | 184 | /// Stops a `Playable` item. 185 | public class func stopItem(_ playableItem: Playable) { 186 | playableItem.stop() 187 | } 188 | 189 | /// Stops a sound with the given identifier. 190 | /// 191 | /// - parameter identifier: The identifier of the item to stop. 192 | public class func stopSound(with identifier: String? = nil) { 193 | for (_, playableItem) in shared.playingSounds { 194 | if playableItem.identifier == identifier { 195 | stopItem(playableItem) 196 | return 197 | } 198 | } 199 | } 200 | 201 | /// Stops all the sounds currently playing by the `Soundable` library. If the `groupKey` 202 | /// parameter is set, the function only stops the sounds grouped under the group key. 203 | /// 204 | /// - parameter groupKey: The group key whose sounds needs to stop. 205 | public class func stopAll(for groupKey: String? = nil) { 206 | usableItemInPlayingSounds(for: groupKey) { (playableItem) in 207 | stopItem(playableItem) 208 | } 209 | } 210 | 211 | 212 | // MARK: - Muting sounds 213 | /// Mute all the sounds or sound queues currently playing by the library. If the `groupKey` 214 | /// parameter is set, the function only mutes the sounds grouped under the group key. 215 | /// 216 | /// When a muted sound finishes playing the completion closure is called anyway. 217 | /// 218 | /// - parameter groupKey: The group key whose sounds needs to mute. 219 | public class func muteAll(for groupKey: String? = nil) { 220 | usableItemInPlayingSounds(for: groupKey) { (playableItem) in 221 | playableItem.mute() 222 | } 223 | } 224 | 225 | /// Unmute all the sounds or sound queues currently muted (aka volume equal to 0.0). 226 | /// If the `groupKey` parameter is set, the function only unmutes the sounds grouped under 227 | /// the group key. 228 | /// 229 | /// - parameter groupKey: The group key whose sounds needs to mute. 230 | public class func unmuteAll(for groupKey: String? = nil) { 231 | usableItemInPlayingSounds(for: groupKey) { (playableItem) in 232 | playableItem.unmute() 233 | } 234 | } 235 | 236 | 237 | // MARK: - Actions 238 | @objc private func handleInterruption(_ notification: Notification) { 239 | guard let userInfo = notification.userInfo, 240 | let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, 241 | let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { 242 | Soundable.removeAssociatedObject() 243 | return 244 | } 245 | 246 | let completion = objc_getAssociatedObject(self, &associatedSoundInterruptionCompletionKey) as? AVAudioSessionInterruptionClosure 247 | completion?(type, userInfo) 248 | 249 | Soundable.removeAssociatedObject() 250 | } 251 | 252 | 253 | // MARK: - Helpers 254 | private class func removeAssociatedObject() { 255 | objc_setAssociatedObject(self, &associatedSoundInterruptionCompletionKey, nil, .OBJC_ASSOCIATION_COPY_NONATOMIC) 256 | } 257 | 258 | fileprivate class func playItem(_ playableItem: Playable, completion: SoundCompletion? = nil) { 259 | if !Soundable.soundEnabled { 260 | completion?(SBError.playingFailed(reason: .audioDisabled)) 261 | return 262 | } 263 | 264 | addPlayableItem(playableItem) 265 | 266 | playableItem.play(groupKey: playableItem.groupKey, loopsCount: playableItem.loopsCount) { error in 267 | removePlayableItem(playableItem) 268 | completion?(error) 269 | } 270 | } 271 | 272 | fileprivate class func usableItemInPlayingSounds(for groupKey: String? = nil, _ closure: ((_ playableItem: Playable) -> ())) { 273 | for (_, playableItem) in shared.playingSounds { 274 | if let groupKey = groupKey, playableItem.groupKey != groupKey { 275 | continue 276 | } 277 | closure(playableItem) 278 | } 279 | } 280 | 281 | internal class func addPlayableItem(_ playableItem: Playable) { 282 | let identifier = playableItem.identifier 283 | if shared.playingSounds[identifier] == nil { 284 | shared.playingSounds[identifier] = playableItem 285 | } 286 | } 287 | 288 | internal class func removePlayableItem(_ playableItem: Playable) { 289 | shared.playingSounds.removeValue(forKey: playableItem.identifier) 290 | } 291 | } 292 | 293 | 294 | extension Sequence where Iterator.Element == Sound { 295 | /// Plays the array of `Sound` objects. 296 | /// 297 | /// - parameter groupKey: The group where the sounds will be played. 298 | /// - parameter loopsCount: The number of times the sounds will be played before calling 299 | /// the completion closure. 300 | /// - parameter completion: The completion closure called after the sounds has finished playing. 301 | public func play(groupKey: String = SoundableKey.DefaultGroupKey, loopsCount: Int = 0, completion: SoundCompletion? = nil) { 302 | let sounds = self as! [Sound] 303 | if sounds.count == 0 { 304 | completion?(SBError.playingFailed(reason: .noSoundsToPlay)) 305 | return 306 | } 307 | 308 | Soundable.play(sounds: sounds, groupKey: groupKey, loopsCount: loopsCount, completion: completion) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /Example/iOS Example/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 42 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | 105 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /Example/iOS Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA038CE621CBEE85009D99B9 /* Soundable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD2442821C2A4CF008B1EFE /* Soundable.framework */; }; 11 | AA038CE721CBEE85009D99B9 /* Soundable.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AAD2442821C2A4CF008B1EFE /* Soundable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | AA5E3F5321C283D80087B56F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3F5221C283D80087B56F /* AppDelegate.swift */; }; 13 | AA5E3F5521C283D80087B56F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3F5421C283D80087B56F /* ViewController.swift */; }; 14 | AA5E3F5821C283D80087B56F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3F5621C283D80087B56F /* Main.storyboard */; }; 15 | AA5E3F5A21C283D90087B56F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3F5921C283D90087B56F /* Assets.xcassets */; }; 16 | AA5E3F5D21C283D90087B56F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3F5B21C283D90087B56F /* LaunchScreen.storyboard */; }; 17 | AA5E3FBA21C28DBA0087B56F /* SoundCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FB921C28DBA0087B56F /* SoundCell.swift */; }; 18 | AA5E3FC221C29BDE0087B56F /* rain.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FBC21C29BDE0087B56F /* rain.mp3 */; }; 19 | AA5E3FC321C29BDE0087B56F /* guitar-songs.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FBD21C29BDE0087B56F /* guitar-songs.mp3 */; }; 20 | AA5E3FC421C29BDE0087B56F /* water-stream.wav in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FBE21C29BDE0087B56F /* water-stream.wav */; }; 21 | AA5E3FC521C29BDE0087B56F /* nature.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FBF21C29BDE0087B56F /* nature.mp3 */; }; 22 | AA5E3FC621C29BDE0087B56F /* guitar-chord.wav in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FC021C29BDE0087B56F /* guitar-chord.wav */; }; 23 | AA5E3FC721C29BDE0087B56F /* rain-thunder.ogg in Resources */ = {isa = PBXBuildFile; fileRef = AA5E3FC121C29BDE0087B56F /* rain-thunder.ogg */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | AA038CE821CBEE85009D99B9 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | AA038CE721CBEE85009D99B9 /* Soundable.framework in Embed Frameworks */, 34 | ); 35 | name = "Embed Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | AA5E3F4F21C283D80087B56F /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | AA5E3F5221C283D80087B56F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | AA5E3F5421C283D80087B56F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 44 | AA5E3F5721C283D80087B56F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | AA5E3F5921C283D90087B56F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | AA5E3F5C21C283D90087B56F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | AA5E3F5E21C283D90087B56F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | AA5E3FB921C28DBA0087B56F /* SoundCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoundCell.swift; sourceTree = ""; }; 49 | AA5E3FBC21C29BDE0087B56F /* rain.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = rain.mp3; path = "iOS Example/Resources/Sounds/rain.mp3"; sourceTree = SOURCE_ROOT; }; 50 | AA5E3FBD21C29BDE0087B56F /* guitar-songs.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = "guitar-songs.mp3"; path = "iOS Example/Resources/Sounds/guitar-songs.mp3"; sourceTree = SOURCE_ROOT; }; 51 | AA5E3FBE21C29BDE0087B56F /* water-stream.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "water-stream.wav"; path = "iOS Example/Resources/Sounds/water-stream.wav"; sourceTree = SOURCE_ROOT; }; 52 | AA5E3FBF21C29BDE0087B56F /* nature.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = nature.mp3; path = "iOS Example/Resources/Sounds/nature.mp3"; sourceTree = SOURCE_ROOT; }; 53 | AA5E3FC021C29BDE0087B56F /* guitar-chord.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "guitar-chord.wav"; path = "iOS Example/Resources/Sounds/guitar-chord.wav"; sourceTree = SOURCE_ROOT; }; 54 | AA5E3FC121C29BDE0087B56F /* rain-thunder.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = "rain-thunder.ogg"; path = "iOS Example/Resources/Sounds/rain-thunder.ogg"; sourceTree = SOURCE_ROOT; }; 55 | AAD2442821C2A4CF008B1EFE /* Soundable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Soundable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | AA5E3F4C21C283D80087B56F /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | AA038CE621CBEE85009D99B9 /* Soundable.framework in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | AA5E3F4621C283D80087B56F = { 71 | isa = PBXGroup; 72 | children = ( 73 | AAD2442821C2A4CF008B1EFE /* Soundable.framework */, 74 | AA5E3F5121C283D80087B56F /* iOS Example */, 75 | AA5E3F5021C283D80087B56F /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | AA5E3F5021C283D80087B56F /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | AA5E3F4F21C283D80087B56F /* iOS Example.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | AA5E3F5121C283D80087B56F /* iOS Example */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | AA5E3F5221C283D80087B56F /* AppDelegate.swift */, 91 | AA5E3F5421C283D80087B56F /* ViewController.swift */, 92 | AA5E3FB921C28DBA0087B56F /* SoundCell.swift */, 93 | AA5E3F6521C2849D0087B56F /* Resources */, 94 | AA5E3F6621C284A80087B56F /* Supporting files */, 95 | ); 96 | path = "iOS Example"; 97 | sourceTree = ""; 98 | }; 99 | AA5E3F6521C2849D0087B56F /* Resources */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | AA5E3FBB21C29BD30087B56F /* Sounds */, 103 | AA5E3F5921C283D90087B56F /* Assets.xcassets */, 104 | AA5E3F5621C283D80087B56F /* Main.storyboard */, 105 | AA5E3F5B21C283D90087B56F /* LaunchScreen.storyboard */, 106 | ); 107 | path = Resources; 108 | sourceTree = ""; 109 | }; 110 | AA5E3F6621C284A80087B56F /* Supporting files */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | AA5E3F5E21C283D90087B56F /* Info.plist */, 114 | ); 115 | path = "Supporting files"; 116 | sourceTree = ""; 117 | }; 118 | AA5E3FBB21C29BD30087B56F /* Sounds */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | AA5E3FC021C29BDE0087B56F /* guitar-chord.wav */, 122 | AA5E3FBD21C29BDE0087B56F /* guitar-songs.mp3 */, 123 | AA5E3FBF21C29BDE0087B56F /* nature.mp3 */, 124 | AA5E3FC121C29BDE0087B56F /* rain-thunder.ogg */, 125 | AA5E3FBC21C29BDE0087B56F /* rain.mp3 */, 126 | AA5E3FBE21C29BDE0087B56F /* water-stream.wav */, 127 | ); 128 | path = Sounds; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | AA5E3F4E21C283D80087B56F /* iOS Example */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = AA5E3F6121C283D90087B56F /* Build configuration list for PBXNativeTarget "iOS Example" */; 137 | buildPhases = ( 138 | AA5E3F4B21C283D80087B56F /* Sources */, 139 | AA5E3F4C21C283D80087B56F /* Frameworks */, 140 | AA5E3F4D21C283D80087B56F /* Resources */, 141 | AA038CE821CBEE85009D99B9 /* Embed Frameworks */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = "iOS Example"; 148 | productName = "iOS Example"; 149 | productReference = AA5E3F4F21C283D80087B56F /* iOS Example.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | AA5E3F4721C283D80087B56F /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastSwiftUpdateCheck = 1010; 159 | LastUpgradeCheck = 1010; 160 | ORGANIZATIONNAME = ThXou; 161 | TargetAttributes = { 162 | AA5E3F4E21C283D80087B56F = { 163 | CreatedOnToolsVersion = 10.1; 164 | SystemCapabilities = { 165 | com.apple.BackgroundModes = { 166 | enabled = 1; 167 | }; 168 | }; 169 | }; 170 | }; 171 | }; 172 | buildConfigurationList = AA5E3F4A21C283D80087B56F /* Build configuration list for PBXProject "iOS Example" */; 173 | compatibilityVersion = "Xcode 9.3"; 174 | developmentRegion = en; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | Base, 179 | ); 180 | mainGroup = AA5E3F4621C283D80087B56F; 181 | productRefGroup = AA5E3F5021C283D80087B56F /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | AA5E3F4E21C283D80087B56F /* iOS Example */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | AA5E3F4D21C283D80087B56F /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | AA5E3FC221C29BDE0087B56F /* rain.mp3 in Resources */, 196 | AA5E3F5D21C283D90087B56F /* LaunchScreen.storyboard in Resources */, 197 | AA5E3FC421C29BDE0087B56F /* water-stream.wav in Resources */, 198 | AA5E3FC721C29BDE0087B56F /* rain-thunder.ogg in Resources */, 199 | AA5E3FC521C29BDE0087B56F /* nature.mp3 in Resources */, 200 | AA5E3F5A21C283D90087B56F /* Assets.xcassets in Resources */, 201 | AA5E3FC621C29BDE0087B56F /* guitar-chord.wav in Resources */, 202 | AA5E3FC321C29BDE0087B56F /* guitar-songs.mp3 in Resources */, 203 | AA5E3F5821C283D80087B56F /* Main.storyboard in Resources */, 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | /* End PBXResourcesBuildPhase section */ 208 | 209 | /* Begin PBXSourcesBuildPhase section */ 210 | AA5E3F4B21C283D80087B56F /* Sources */ = { 211 | isa = PBXSourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | AA5E3FBA21C28DBA0087B56F /* SoundCell.swift in Sources */, 215 | AA5E3F5521C283D80087B56F /* ViewController.swift in Sources */, 216 | AA5E3F5321C283D80087B56F /* AppDelegate.swift in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | AA5E3F5621C283D80087B56F /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | AA5E3F5721C283D80087B56F /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | AA5E3F5B21C283D90087B56F /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | AA5E3F5C21C283D90087B56F /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | AA5E3F5F21C283D90087B56F /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_ENABLE_OBJC_WEAK = YES; 253 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_COMMA = YES; 256 | CLANG_WARN_CONSTANT_CONVERSION = YES; 257 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 260 | CLANG_WARN_EMPTY_BODY = YES; 261 | CLANG_WARN_ENUM_CONVERSION = YES; 262 | CLANG_WARN_INFINITE_RECURSION = YES; 263 | CLANG_WARN_INT_CONVERSION = YES; 264 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 266 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 269 | CLANG_WARN_STRICT_PROTOTYPES = YES; 270 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 271 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | CODE_SIGN_IDENTITY = "iPhone Developer"; 275 | COPY_PHASE_STRIP = NO; 276 | DEBUG_INFORMATION_FORMAT = dwarf; 277 | ENABLE_STRICT_OBJC_MSGSEND = YES; 278 | ENABLE_TESTABILITY = YES; 279 | GCC_C_LANGUAGE_STANDARD = gnu11; 280 | GCC_DYNAMIC_NO_PIC = NO; 281 | GCC_NO_COMMON_BLOCKS = YES; 282 | GCC_OPTIMIZATION_LEVEL = 0; 283 | GCC_PREPROCESSOR_DEFINITIONS = ( 284 | "DEBUG=1", 285 | "$(inherited)", 286 | ); 287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 294 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 295 | MTL_FAST_MATH = YES; 296 | ONLY_ACTIVE_ARCH = YES; 297 | SDKROOT = iphoneos; 298 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 300 | }; 301 | name = Debug; 302 | }; 303 | AA5E3F6021C283D90087B56F /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ALWAYS_SEARCH_USER_PATHS = NO; 307 | CLANG_ANALYZER_NONNULL = YES; 308 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_ENABLE_OBJC_WEAK = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | CODE_SIGN_IDENTITY = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu11; 341 | GCC_NO_COMMON_BLOCKS = YES; 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 349 | MTL_ENABLE_DEBUG_INFO = NO; 350 | MTL_FAST_MATH = YES; 351 | SDKROOT = iphoneos; 352 | SWIFT_COMPILATION_MODE = wholemodule; 353 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Release; 357 | }; 358 | AA5E3F6221C283D90087B56F /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 362 | CODE_SIGN_STYLE = Automatic; 363 | DEVELOPMENT_TEAM = BZ3VSUA92U; 364 | INFOPLIST_FILE = "$(SRCROOT)/iOS Example/Supporting files/Info.plist"; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/Frameworks", 368 | ); 369 | PRODUCT_BUNDLE_IDENTIFIER = "com.thxou.iOS-Example"; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | SWIFT_VERSION = 4.2; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | }; 374 | name = Debug; 375 | }; 376 | AA5E3F6321C283D90087B56F /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | CODE_SIGN_STYLE = Automatic; 381 | DEVELOPMENT_TEAM = BZ3VSUA92U; 382 | INFOPLIST_FILE = "$(SRCROOT)/iOS Example/Supporting files/Info.plist"; 383 | LD_RUNPATH_SEARCH_PATHS = ( 384 | "$(inherited)", 385 | "@executable_path/Frameworks", 386 | ); 387 | PRODUCT_BUNDLE_IDENTIFIER = "com.thxou.iOS-Example"; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_VERSION = 4.2; 390 | TARGETED_DEVICE_FAMILY = "1,2"; 391 | }; 392 | name = Release; 393 | }; 394 | /* End XCBuildConfiguration section */ 395 | 396 | /* Begin XCConfigurationList section */ 397 | AA5E3F4A21C283D80087B56F /* Build configuration list for PBXProject "iOS Example" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | AA5E3F5F21C283D90087B56F /* Debug */, 401 | AA5E3F6021C283D90087B56F /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | AA5E3F6121C283D90087B56F /* Build configuration list for PBXNativeTarget "iOS Example" */ = { 407 | isa = XCConfigurationList; 408 | buildConfigurations = ( 409 | AA5E3F6221C283D90087B56F /* Debug */, 410 | AA5E3F6321C283D90087B56F /* Release */, 411 | ); 412 | defaultConfigurationIsVisible = 0; 413 | defaultConfigurationName = Release; 414 | }; 415 | /* End XCConfigurationList section */ 416 | }; 417 | rootObject = AA5E3F4721C283D80087B56F /* Project object */; 418 | } 419 | -------------------------------------------------------------------------------- /Soundable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA5E3F9421C286530087B56F /* Soundable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA5E3F8B21C286520087B56F /* Soundable.framework */; }; 11 | AA5E3F9B21C286530087B56F /* Soundable.h in Headers */ = {isa = PBXBuildFile; fileRef = AA5E3F8D21C286530087B56F /* Soundable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | AA5E3FA821C2868E0087B56F /* Playable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FA321C2868E0087B56F /* Playable.swift */; }; 13 | AA5E3FA921C2868E0087B56F /* SBError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FA421C2868E0087B56F /* SBError.swift */; }; 14 | AA5E3FAA21C2868E0087B56F /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FA521C2868E0087B56F /* Sound.swift */; }; 15 | AA5E3FAB21C2868E0087B56F /* SoundsQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FA621C2868E0087B56F /* SoundsQueue.swift */; }; 16 | AA5E3FAC21C2868E0087B56F /* Soundable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5E3FA721C2868E0087B56F /* Soundable.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | AA5E3F9521C286530087B56F /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = AA5E3F6821C285450087B56F /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = AA5E3F8A21C286520087B56F; 25 | remoteInfo = "Soundable iOS"; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | AA5E3F8B21C286520087B56F /* Soundable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Soundable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | AA5E3F8D21C286530087B56F /* Soundable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Soundable.h; sourceTree = ""; }; 32 | AA5E3F8E21C286530087B56F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 33 | AA5E3F9321C286530087B56F /* Soundable iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Soundable iOS Tests.xctest"; path = "Soundable iOS Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | AA5E3F9A21C286530087B56F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | AA5E3FA321C2868E0087B56F /* Playable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Playable.swift; path = Source/Playable.swift; sourceTree = SOURCE_ROOT; }; 36 | AA5E3FA421C2868E0087B56F /* SBError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SBError.swift; path = Source/SBError.swift; sourceTree = SOURCE_ROOT; }; 37 | AA5E3FA521C2868E0087B56F /* Sound.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Sound.swift; path = Source/Sound.swift; sourceTree = SOURCE_ROOT; }; 38 | AA5E3FA621C2868E0087B56F /* SoundsQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SoundsQueue.swift; path = Source/SoundsQueue.swift; sourceTree = SOURCE_ROOT; }; 39 | AA5E3FA721C2868E0087B56F /* Soundable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Soundable.swift; path = Source/Soundable.swift; sourceTree = SOURCE_ROOT; }; 40 | AAD2442221C2A1BB008B1EFE /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = ""; }; 41 | AAD2442321C2A1BB008B1EFE /* Soundable.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Soundable.podspec; sourceTree = ""; }; 42 | AAD2442421C2A1BB008B1EFE /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | AA5E3F8821C286520087B56F /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | AA5E3F9021C286530087B56F /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | AA5E3F9421C286530087B56F /* Soundable.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | AA5E3F6721C285450087B56F = { 65 | isa = PBXGroup; 66 | children = ( 67 | AA5E3F8521C285FC0087B56F /* Deployment */, 68 | AA5E3FA221C286740087B56F /* Source */, 69 | AA5E3F9721C286530087B56F /* Tests */, 70 | AA5E3F7121C285450087B56F /* Products */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | AA5E3F7121C285450087B56F /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | AA5E3F8B21C286520087B56F /* Soundable.framework */, 78 | AA5E3F9321C286530087B56F /* Soundable iOS Tests.xctest */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | AA5E3F8521C285FC0087B56F /* Deployment */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | AAD2442421C2A1BB008B1EFE /* LICENSE */, 87 | AAD2442321C2A1BB008B1EFE /* Soundable.podspec */, 88 | AAD2442221C2A1BB008B1EFE /* VERSION */, 89 | ); 90 | name = Deployment; 91 | sourceTree = ""; 92 | }; 93 | AA5E3F9721C286530087B56F /* Tests */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | AA5E3FAD21C287280087B56F /* Supporting files */, 97 | ); 98 | path = Tests; 99 | sourceTree = ""; 100 | }; 101 | AA5E3FA221C286740087B56F /* Source */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | AA5E3FA321C2868E0087B56F /* Playable.swift */, 105 | AA5E3FA421C2868E0087B56F /* SBError.swift */, 106 | AA5E3FA521C2868E0087B56F /* Sound.swift */, 107 | AA5E3FA721C2868E0087B56F /* Soundable.swift */, 108 | AA5E3FA621C2868E0087B56F /* SoundsQueue.swift */, 109 | AA5E3FAE21C2878A0087B56F /* Supporting files */, 110 | ); 111 | path = Source; 112 | sourceTree = ""; 113 | }; 114 | AA5E3FAD21C287280087B56F /* Supporting files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | AA5E3F9A21C286530087B56F /* Info.plist */, 118 | ); 119 | name = "Supporting files"; 120 | sourceTree = ""; 121 | }; 122 | AA5E3FAE21C2878A0087B56F /* Supporting files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | AA5E3F8E21C286530087B56F /* Info.plist */, 126 | AA5E3F8D21C286530087B56F /* Soundable.h */, 127 | ); 128 | name = "Supporting files"; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXHeadersBuildPhase section */ 134 | AA5E3F8621C286520087B56F /* Headers */ = { 135 | isa = PBXHeadersBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | AA5E3F9B21C286530087B56F /* Soundable.h in Headers */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXHeadersBuildPhase section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | AA5E3F8A21C286520087B56F /* Soundable iOS */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = AA5E3F9C21C286530087B56F /* Build configuration list for PBXNativeTarget "Soundable iOS" */; 148 | buildPhases = ( 149 | AA5E3F8621C286520087B56F /* Headers */, 150 | AA5E3F8721C286520087B56F /* Sources */, 151 | AA5E3F8821C286520087B56F /* Frameworks */, 152 | AA5E3F8921C286520087B56F /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = "Soundable iOS"; 159 | productName = "Soundable iOS"; 160 | productReference = AA5E3F8B21C286520087B56F /* Soundable.framework */; 161 | productType = "com.apple.product-type.framework"; 162 | }; 163 | AA5E3F9221C286530087B56F /* Soundable iOS Tests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = AA5E3F9F21C286530087B56F /* Build configuration list for PBXNativeTarget "Soundable iOS Tests" */; 166 | buildPhases = ( 167 | AA5E3F8F21C286530087B56F /* Sources */, 168 | AA5E3F9021C286530087B56F /* Frameworks */, 169 | AA5E3F9121C286530087B56F /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | AA5E3F9621C286530087B56F /* PBXTargetDependency */, 175 | ); 176 | name = "Soundable iOS Tests"; 177 | productName = "Soundable iOSTests"; 178 | productReference = AA5E3F9321C286530087B56F /* Soundable iOS Tests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | /* End PBXNativeTarget section */ 182 | 183 | /* Begin PBXProject section */ 184 | AA5E3F6821C285450087B56F /* Project object */ = { 185 | isa = PBXProject; 186 | attributes = { 187 | LastSwiftUpdateCheck = 1010; 188 | LastUpgradeCheck = 1010; 189 | ORGANIZATIONNAME = ThXou; 190 | TargetAttributes = { 191 | AA5E3F8A21C286520087B56F = { 192 | CreatedOnToolsVersion = 10.1; 193 | LastSwiftMigration = 1010; 194 | }; 195 | AA5E3F9221C286530087B56F = { 196 | CreatedOnToolsVersion = 10.1; 197 | }; 198 | }; 199 | }; 200 | buildConfigurationList = AA5E3F6B21C285450087B56F /* Build configuration list for PBXProject "Soundable" */; 201 | compatibilityVersion = "Xcode 9.3"; 202 | developmentRegion = en; 203 | hasScannedForEncodings = 0; 204 | knownRegions = ( 205 | en, 206 | Base, 207 | ); 208 | mainGroup = AA5E3F6721C285450087B56F; 209 | productRefGroup = AA5E3F7121C285450087B56F /* Products */; 210 | projectDirPath = ""; 211 | projectRoot = ""; 212 | targets = ( 213 | AA5E3F8A21C286520087B56F /* Soundable iOS */, 214 | AA5E3F9221C286530087B56F /* Soundable iOS Tests */, 215 | ); 216 | }; 217 | /* End PBXProject section */ 218 | 219 | /* Begin PBXResourcesBuildPhase section */ 220 | AA5E3F8921C286520087B56F /* Resources */ = { 221 | isa = PBXResourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | AA5E3F9121C286530087B56F /* Resources */ = { 228 | isa = PBXResourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXResourcesBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | AA5E3F8721C286520087B56F /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | AA5E3FA821C2868E0087B56F /* Playable.swift in Sources */, 242 | AA5E3FAB21C2868E0087B56F /* SoundsQueue.swift in Sources */, 243 | AA5E3FAA21C2868E0087B56F /* Sound.swift in Sources */, 244 | AA5E3FA921C2868E0087B56F /* SBError.swift in Sources */, 245 | AA5E3FAC21C2868E0087B56F /* Soundable.swift in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | AA5E3F8F21C286530087B56F /* Sources */ = { 250 | isa = PBXSourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXSourcesBuildPhase section */ 257 | 258 | /* Begin PBXTargetDependency section */ 259 | AA5E3F9621C286530087B56F /* PBXTargetDependency */ = { 260 | isa = PBXTargetDependency; 261 | target = AA5E3F8A21C286520087B56F /* Soundable iOS */; 262 | targetProxy = AA5E3F9521C286530087B56F /* PBXContainerItemProxy */; 263 | }; 264 | /* End PBXTargetDependency section */ 265 | 266 | /* Begin XCBuildConfiguration section */ 267 | AA5E3F8021C285460087B56F /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_ENABLE_OBJC_WEAK = YES; 278 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_COMMA = YES; 281 | CLANG_WARN_CONSTANT_CONVERSION = YES; 282 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INFINITE_RECURSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 291 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 294 | CLANG_WARN_STRICT_PROTOTYPES = YES; 295 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 296 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | CODE_SIGN_IDENTITY = "iPhone Developer"; 300 | COPY_PHASE_STRIP = NO; 301 | DEBUG_INFORMATION_FORMAT = dwarf; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | ENABLE_TESTABILITY = YES; 304 | GCC_C_LANGUAGE_STANDARD = gnu11; 305 | GCC_DYNAMIC_NO_PIC = NO; 306 | GCC_NO_COMMON_BLOCKS = YES; 307 | GCC_OPTIMIZATION_LEVEL = 0; 308 | GCC_PREPROCESSOR_DEFINITIONS = ( 309 | "DEBUG=1", 310 | "$(inherited)", 311 | ); 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | INFOPLIST_FILE = Source/Info.plist; 319 | INFOPLIST_OUTPUT_FORMAT = binary; 320 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 321 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 322 | MTL_FAST_MATH = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | PLIST_FILE_OUTPUT_FORMAT = binary; 325 | PRODUCT_BUNDLE_IDENTIFIER = com.thxou.Soundable; 326 | PRODUCT_NAME = Soundable; 327 | PUBLIC_HEADERS_FOLDER_PATH = "$(CONTENTS_FOLDER_PATH)/Headers"; 328 | SDKROOT = iphoneos; 329 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 331 | WRAPPER_EXTENSION = framework; 332 | }; 333 | name = Debug; 334 | }; 335 | AA5E3F8121C285460087B56F /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ALWAYS_SEARCH_USER_PATHS = NO; 339 | CLANG_ANALYZER_NONNULL = YES; 340 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_ENABLE_OBJC_WEAK = YES; 346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 347 | CLANG_WARN_BOOL_CONVERSION = YES; 348 | CLANG_WARN_COMMA = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 365 | CLANG_WARN_UNREACHABLE_CODE = YES; 366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 367 | CODE_SIGN_IDENTITY = "iPhone Developer"; 368 | COPY_PHASE_STRIP = NO; 369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu11; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | INFOPLIST_FILE = Source/Info.plist; 381 | INFOPLIST_OUTPUT_FORMAT = binary; 382 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 383 | MTL_ENABLE_DEBUG_INFO = NO; 384 | MTL_FAST_MATH = YES; 385 | PLIST_FILE_OUTPUT_FORMAT = binary; 386 | PRODUCT_BUNDLE_IDENTIFIER = com.thxou.Soundable; 387 | PRODUCT_NAME = Soundable; 388 | PUBLIC_HEADERS_FOLDER_PATH = "$(CONTENTS_FOLDER_PATH)/Headers"; 389 | SDKROOT = iphoneos; 390 | SWIFT_COMPILATION_MODE = wholemodule; 391 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 392 | VALIDATE_PRODUCT = YES; 393 | WRAPPER_EXTENSION = framework; 394 | }; 395 | name = Release; 396 | }; 397 | AA5E3F9D21C286530087B56F /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | CLANG_ENABLE_MODULES = YES; 401 | CODE_SIGN_IDENTITY = ""; 402 | CODE_SIGN_STYLE = Automatic; 403 | CURRENT_PROJECT_VERSION = 1; 404 | DEFINES_MODULE = YES; 405 | DEVELOPMENT_TEAM = BZ3VSUA92U; 406 | DYLIB_COMPATIBILITY_VERSION = 1; 407 | DYLIB_CURRENT_VERSION = 1; 408 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 409 | INFOPLIST_FILE = Source/Info.plist; 410 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 411 | LD_RUNPATH_SEARCH_PATHS = ( 412 | "$(inherited)", 413 | "@executable_path/Frameworks", 414 | "@loader_path/Frameworks", 415 | ); 416 | PRODUCT_BUNDLE_IDENTIFIER = com.thxou.soundable; 417 | PRODUCT_NAME = Soundable; 418 | SKIP_INSTALL = YES; 419 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 420 | SWIFT_VERSION = 4.2; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | VERSIONING_SYSTEM = "apple-generic"; 423 | VERSION_INFO_PREFIX = ""; 424 | }; 425 | name = Debug; 426 | }; 427 | AA5E3F9E21C286530087B56F /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | CLANG_ENABLE_MODULES = YES; 431 | CODE_SIGN_IDENTITY = ""; 432 | CODE_SIGN_STYLE = Automatic; 433 | CURRENT_PROJECT_VERSION = 1; 434 | DEFINES_MODULE = YES; 435 | DEVELOPMENT_TEAM = BZ3VSUA92U; 436 | DYLIB_COMPATIBILITY_VERSION = 1; 437 | DYLIB_CURRENT_VERSION = 1; 438 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 439 | INFOPLIST_FILE = Source/Info.plist; 440 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 441 | LD_RUNPATH_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "@executable_path/Frameworks", 444 | "@loader_path/Frameworks", 445 | ); 446 | PRODUCT_BUNDLE_IDENTIFIER = com.thxou.soundable; 447 | PRODUCT_NAME = Soundable; 448 | SKIP_INSTALL = YES; 449 | SWIFT_VERSION = 4.2; 450 | TARGETED_DEVICE_FAMILY = "1,2"; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | VERSION_INFO_PREFIX = ""; 453 | }; 454 | name = Release; 455 | }; 456 | AA5E3FA021C286530087B56F /* Debug */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 460 | CODE_SIGN_STYLE = Automatic; 461 | DEVELOPMENT_TEAM = BZ3VSUA92U; 462 | INFOPLIST_FILE = "Soundable iOSTests/Info.plist"; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/Frameworks", 466 | "@loader_path/Frameworks", 467 | ); 468 | PRODUCT_BUNDLE_IDENTIFIER = "com.thxou.Soundable-iOSTests"; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_VERSION = 4.2; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | }; 473 | name = Debug; 474 | }; 475 | AA5E3FA121C286530087B56F /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 479 | CODE_SIGN_STYLE = Automatic; 480 | DEVELOPMENT_TEAM = BZ3VSUA92U; 481 | INFOPLIST_FILE = "Soundable iOSTests/Info.plist"; 482 | LD_RUNPATH_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "@executable_path/Frameworks", 485 | "@loader_path/Frameworks", 486 | ); 487 | PRODUCT_BUNDLE_IDENTIFIER = "com.thxou.Soundable-iOSTests"; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | SWIFT_VERSION = 4.2; 490 | TARGETED_DEVICE_FAMILY = "1,2"; 491 | }; 492 | name = Release; 493 | }; 494 | /* End XCBuildConfiguration section */ 495 | 496 | /* Begin XCConfigurationList section */ 497 | AA5E3F6B21C285450087B56F /* Build configuration list for PBXProject "Soundable" */ = { 498 | isa = XCConfigurationList; 499 | buildConfigurations = ( 500 | AA5E3F8021C285460087B56F /* Debug */, 501 | AA5E3F8121C285460087B56F /* Release */, 502 | ); 503 | defaultConfigurationIsVisible = 0; 504 | defaultConfigurationName = Release; 505 | }; 506 | AA5E3F9C21C286530087B56F /* Build configuration list for PBXNativeTarget "Soundable iOS" */ = { 507 | isa = XCConfigurationList; 508 | buildConfigurations = ( 509 | AA5E3F9D21C286530087B56F /* Debug */, 510 | AA5E3F9E21C286530087B56F /* Release */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | AA5E3F9F21C286530087B56F /* Build configuration list for PBXNativeTarget "Soundable iOS Tests" */ = { 516 | isa = XCConfigurationList; 517 | buildConfigurations = ( 518 | AA5E3FA021C286530087B56F /* Debug */, 519 | AA5E3FA121C286530087B56F /* Release */, 520 | ); 521 | defaultConfigurationIsVisible = 0; 522 | defaultConfigurationName = Release; 523 | }; 524 | /* End XCConfigurationList section */ 525 | }; 526 | rootObject = AA5E3F6821C285450087B56F /* Project object */; 527 | } 528 | --------------------------------------------------------------------------------