├── 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 | [](https://github.com/ThXou/Soundable)
5 | [](https://github.com/ThXou/Soundable)
6 | [](https://github.com/Carthage/Carthage)
7 | [](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 |
--------------------------------------------------------------------------------