├── Pod
├── Assets
│ ├── .gitkeep
│ ├── audio_play_icon.png
│ ├── audio_close_icon.png
│ ├── audio_pause_icon.png
│ ├── audio_play_icon@2x.png
│ ├── audio_play_icon@3x.png
│ ├── audio_close_icon@2x.png
│ ├── audio_close_icon@3x.png
│ ├── audio_pause_icon@2x.png
│ ├── audio_pause_icon@3x.png
│ └── Localizable.strings
└── Classes
│ ├── .gitkeep
│ ├── LSPCMTimeHelper.h
│ ├── LSPAudioPlayerModel.h
│ ├── loudspeaker.h
│ ├── LSPAudioPlayerModel.m
│ ├── LSPKit.h
│ ├── LSPProgressView.h
│ ├── LSPAudioView.h
│ ├── LSPAudioPlayer.h
│ ├── LSPCMTimeHelper.m
│ ├── LSPConfiguration.m
│ ├── LSPKit.m
│ ├── LSPConfiguration.h
│ ├── LSPAudioViewController.h
│ ├── LSPProgressView.m
│ ├── LSPAudioPlayer.m
│ ├── LSPAudioView.m
│ └── LSPAudioViewController.m
├── Example
├── .ruby-version
├── Tests
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── Nyan-Cat-Test.mp3
│ ├── Tests-Prefix.pch
│ ├── Tests-Info.plist
│ ├── LSPCMTimeHelperSpec.m
│ ├── LSPConfigurationSpec.m
│ ├── LSPAudioViewSpec.m
│ ├── LSPProgressViewSpec.m
│ └── LSPAudioPlayerSpec.m
├── loudspeaker
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── Nyan-Cat.mp3
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── 1024.png
│ │ │ ├── 60@2x.png
│ │ │ ├── 60@3x.png
│ │ │ ├── 76@1x.png
│ │ │ ├── 76@3x.png
│ │ │ ├── 83.5@2x.png
│ │ │ └── Contents.json
│ ├── LSPAppDelegate.h
│ ├── loudspeaker-Prefix.pch
│ ├── main.m
│ ├── LSPViewController.h
│ ├── loudspeaker-Info.plist
│ ├── LSPAppDelegate.m
│ ├── Launch.storyboard
│ ├── LSPViewController.m
│ └── Base.lproj
│ │ └── Main.storyboard
├── loudspeaker.xcworkspace
│ └── contents.xcworkspacedata
├── loudspeaker.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ ├── Tests.xcscheme
│ │ │ └── loudspeaker.xcscheme
│ └── project.pbxproj
├── .gitignore
├── Podfile
└── Podfile.lock
├── Gemfile
├── .gitignore
├── .travis.yml
├── LICENSE
├── loudspeaker.podspec
├── README.md
├── CHANGELOG.md
└── Gemfile.lock
/Pod/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Pod/Classes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Example/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.4.2
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'xcpretty'
4 | gem 'cocoapods'
5 |
--------------------------------------------------------------------------------
/Example/Tests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/loudspeaker/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Pod/Assets/audio_play_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_play_icon.png
--------------------------------------------------------------------------------
/Example/Tests/Nyan-Cat-Test.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/Tests/Nyan-Cat-Test.mp3
--------------------------------------------------------------------------------
/Example/loudspeaker/Nyan-Cat.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Nyan-Cat.mp3
--------------------------------------------------------------------------------
/Pod/Assets/audio_close_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_close_icon.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_pause_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_pause_icon.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_play_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_play_icon@2x.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_play_icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_play_icon@3x.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_close_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_close_icon@2x.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_close_icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_close_icon@3x.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_pause_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_pause_icon@2x.png
--------------------------------------------------------------------------------
/Pod/Assets/audio_pause_icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Pod/Assets/audio_pause_icon@3x.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/60@2x.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/60@3x.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/76@1x.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/76@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/76@3x.png
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amco/loudspeaker/HEAD/Example/loudspeaker/Images.xcassets/AppIcon.appiconset/83.5@2x.png
--------------------------------------------------------------------------------
/Example/loudspeaker.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Example/loudspeaker.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every test case source file.
5 | //
6 |
7 | #ifdef __OBJC__
8 |
9 | #define EXP_SHORTHAND
10 | #import
11 | #import
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPCMTimeHelper.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPCMTimeHelper.h
3 | //
4 | // Created by Adam Yanalunas on 12/11/13.
5 | // Copyright (c) 2013 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | @interface LSPCMTimeHelper : NSObject
11 |
12 | + (NSString *)readableCMTime:(CMTime)time;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # CocoaPods
26 | Pods/
27 |
--------------------------------------------------------------------------------
/Example/loudspeaker/LSPAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAppDelegate.h
3 | // loudspeaker
4 | //
5 | // Created by CocoaPods on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface LSPAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | osx_image: xcode9
2 | language: objective-c
3 | podfile: Example/Podfile
4 | bundler_args: "--without development --deployment"
5 | cache:
6 | - bundler
7 | - cocoapods
8 | script:
9 | - set -o pipefail && xcodebuild -workspace Example/loudspeaker.xcworkspace -scheme Tests -configuration Debug clean build test -sdk iphonesimulator -destination platform="iOS Simulator,OS=latest,name=iPhone X" | xcpretty -c
10 |
--------------------------------------------------------------------------------
/Pod/Assets/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | Pods
4 |
5 | Created by Adam Yanalunas on 11/19/17.
6 |
7 | */
8 |
9 | "loudspeaker.close" = "Close";
10 | "loudspeaker.pause" = "Pause";
11 | "loudspeaker.play" = "Play";
12 | "loudspeaker.seconds_elapsed_format" = "%@ seconds elapsed";
13 | "loudspeaker.tap_to_pause_playback" = "Tap to pause playback";
14 | "loudspeaker.tap_to_resume_playback" = "Tap to resume playback";
15 |
--------------------------------------------------------------------------------
/Example/loudspeaker/loudspeaker-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | #import
15 | #import
16 | #endif
17 |
--------------------------------------------------------------------------------
/Example/loudspeaker/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "LSPAppDelegate.h"
12 |
13 | int main(int argc, char * argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([LSPAppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioPlayerModel.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayerModel.h
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 11/16/17.
6 | //
7 |
8 |
9 | #import
10 |
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 |
15 | @interface LSPAudioPlayerModel : NSObject
16 |
17 | - (nullable NSURL *)destination;
18 | - (void)setDestination:(NSURL *)url;
19 | - (nullable NSString *)title;
20 |
21 | @end
22 |
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/Pod/Classes/loudspeaker.h:
--------------------------------------------------------------------------------
1 | //
2 | // loudspeaker.h
3 | // Pods
4 | //
5 | // Created by Adam Yanalunas on 11/17/17.
6 | //
7 |
8 | #ifndef loudspeaker_h
9 | #define loudspeaker_h
10 |
11 | #import "LSPAudioPlayer.h"
12 | #import "LSPAudioPlayerModel.h"
13 | #import "LSPAudioView.h"
14 | #import "LSPAudioViewController.h"
15 | #import "LSPCMTimeHelper.h"
16 | #import "LSPConfiguration.h"
17 | #import "LSPKit.h"
18 | #import "LSPProgressView.h"
19 |
20 | #endif /* loudspeaker_h */
21 |
--------------------------------------------------------------------------------
/Example/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | *.xcuserstate
4 | *.xcbkptlist
5 | *.xcuserstate
6 | */build/*
7 | Build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | *.xcworkspace
17 | !default.xcworkspace
18 | xcuserdata
19 | profile
20 | *.moved-aside
21 | DerivedData
22 | .idea/
23 | *.hmap
24 | *.xcuserstate
25 | *.xcbkptlist
26 | .LSOverride
27 |
28 | # Cocoapods
29 | Pods
30 |
31 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioPlayerModel.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayerModel.m
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 11/16/17.
6 | //
7 |
8 | #import "LSPAudioPlayerModel.h"
9 |
10 |
11 | @interface LSPAudioPlayerModel ()
12 |
13 | @property (nonatomic) NSURL *url;
14 |
15 | @end
16 |
17 |
18 | @implementation LSPAudioPlayerModel
19 |
20 |
21 | - (NSString *)title
22 | {
23 | return self.url.lastPathComponent;
24 | }
25 |
26 |
27 | - (NSURL *)destination
28 | {
29 | return self.destination;
30 | }
31 |
32 |
33 | - (void)setDestination:(NSURL *)url
34 | {
35 | self.url = url;
36 | }
37 |
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/Example/loudspeaker/LSPViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPViewController.h
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @interface LSPViewController : UIViewController
13 |
14 | @property (weak, nonatomic) IBOutlet UIButton *demoButton;
15 | @property (weak, nonatomic) IBOutlet UIButton *harlequinButton;
16 |
17 | - (void)resetDemoButtons;
18 | - (IBAction)launchDemo:(id)sender;
19 | - (IBAction)launchHarlequinDemo:(id)sender;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 |
3 | platform :ios, '9.0'
4 | use_frameworks!
5 | inhibit_all_warnings!
6 |
7 | target 'loudspeaker' do
8 | pod 'loudspeaker', :path => '../'
9 |
10 | target 'Tests' do
11 | inherit! :search_paths
12 |
13 | pod 'Specta'
14 | pod 'Expecta'
15 | pod 'OCMock'
16 | end
17 | end
18 |
19 | post_install do |installer|
20 | installer.pods_project.targets.each do |target|
21 | target.build_configurations.each do |config|
22 | config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
23 | config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
24 | config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Expecta (1.0.6)
3 | - loudspeaker (1.0.0):
4 | - Masonry (~> 1.1.0)
5 | - Masonry (1.1.0)
6 | - OCMock (3.4)
7 | - Specta (1.0.7)
8 |
9 | DEPENDENCIES:
10 | - Expecta
11 | - loudspeaker (from `../`)
12 | - OCMock
13 | - Specta
14 |
15 | EXTERNAL SOURCES:
16 | loudspeaker:
17 | :path: ../
18 |
19 | SPEC CHECKSUMS:
20 | Expecta: 3b6bd90a64b9a1dcb0b70aa0e10a7f8f631667d5
21 | loudspeaker: a09f6b0f5af84d056c86c3ad063ded0f90cdb3fc
22 | Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
23 | OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
24 | Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66
25 |
26 | PODFILE CHECKSUM: 6cfa779b26a19b93894470338981de29e1c1fb06
27 |
28 | COCOAPODS: 1.3.1
29 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPKit.h
3 | //
4 | // Created by Adam Yanalunas on 8/29/14.
5 | //
6 | //
7 |
8 | #import
9 |
10 |
11 | #define DELEGATE_SAFELY(obj, selector, ...) \
12 | if ([obj respondsToSelector:selector]) { __VA_ARGS__ }
13 |
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 |
18 | extern NSString *const LSPAudioPlayerStart;
19 | extern NSString *const LSPAudioPlayerStop;
20 |
21 |
22 | @interface LSPKit : NSObject
23 |
24 | + (UIImage *)closeIcon;
25 | + (NSString *)fontName;
26 | + (nullable UIImage *)imageNamed:(NSString *)name type:(NSString *)type;
27 | + (nullable NSString *)localizedString:(NSString *)string;
28 | + (UIImage *)pauseIcon;
29 | + (UIImage *)playIcon;
30 |
31 | @end
32 |
33 |
34 | NS_ASSUME_NONNULL_END
35 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPProgressView.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPProgressView.h
3 | //
4 | // Created by Adam Yanalunas on 2/12/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 |
9 | NS_ASSUME_NONNULL_BEGIN
10 |
11 |
12 | @interface LSPProgressView : UIView
13 |
14 | @property (nonatomic) UIColor *backgroundColor;
15 | @property (nonatomic) UIColor *foregroundColor;
16 | @property (nonatomic) UIView *progressBackground;
17 | @property (nonatomic) UIView *progressBar;
18 |
19 | - (instancetype)initForAutoLayout NS_DESIGNATED_INITIALIZER;
20 | - (instancetype)init NS_UNAVAILABLE;
21 | - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
22 | + (instancetype)new NS_UNAVAILABLE;
23 | + (instancetype)newAutoLayoutView;
24 |
25 | - (void)setProgress:(Float64)amount;
26 | - (void)setup;
27 |
28 | @end
29 |
30 | NS_ASSUME_NONNULL_END
31 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioView.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayer.h
3 | //
4 | // Created by Adam Yanalunas on 2/6/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 |
9 | #import "LSPProgressView.h"
10 |
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 |
15 | @interface LSPAudioView : UIView
16 |
17 | - (instancetype)initForAutoLayout NS_DESIGNATED_INITIALIZER;
18 | - (instancetype)init NS_UNAVAILABLE;
19 | - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
20 | + (instancetype)new NS_UNAVAILABLE;
21 | + (instancetype)newAutoLayoutView;
22 |
23 | @property (nonatomic) UIButton *closeButton;
24 | @property (nonatomic) UILabel *playbackTimeLabel;
25 | @property (nonatomic) UIButton *playPauseButton;
26 | @property (nonatomic) LSPProgressView *progressView;
27 | @property (nonatomic) UILabel *titleLabel;
28 |
29 | - (void)setCurrentProgress:(NSString *)progress forDuration:(NSString *)duration;
30 | - (void)showLayoutForPlaying:(BOOL)isPlaying;
31 |
32 | @end
33 |
34 |
35 | NS_ASSUME_NONNULL_END
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Adam Yanalunas
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioPlayer.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayer.h
3 | //
4 | // Created by Adam Yanalunas on 3/12/13.
5 | // Copyright (c) 2013 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import
9 | #import
10 |
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 |
15 | @interface LSPAudioPlayer : NSObject
16 |
17 | @property (nonatomic, readonly) AVQueuePlayer *player;
18 | @property (nonatomic, nullable) NSURL* previousPath;
19 | @property (nonatomic, readonly, getter = isPlaying) BOOL playing;
20 |
21 | - (id)addProgressObserver:(void (^)(CMTime time))observer;
22 | - (CMTime)currentTime;
23 | - (CMTime)duration;
24 | - (void)jumpToProgress:(Float64)progress;
25 | - (void)play;
26 | - (void)playFromURL:(NSURL*)url;
27 | - (void)playFromURLString:(NSString*)url;
28 | - (void)pause;
29 | - (void)removeProgressObserver:(id)observer;
30 | - (void)resetPlayer;
31 | - (void)setVolume:(float)volume;
32 | - (AVPlayerStatus)status;
33 | - (void)stop;
34 | - (CMTime)timeFromProgress:(Float64)progress;
35 | - (void)togglePlayback;
36 |
37 | @end
38 |
39 |
40 | NS_ASSUME_NONNULL_END
41 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPCMTimeHelper.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPCMTimeHelper.m
3 | //
4 | // Created by Adam Yanalunas on 12/11/13.
5 | // Copyright (c) 2013 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import "LSPCMTimeHelper.h"
9 |
10 | @implementation LSPCMTimeHelper
11 |
12 | + (NSString *)readableCMTime:(CMTime)time
13 | {
14 | NSInteger totalSeconds = CMTimeGetSeconds(time);
15 | if (totalSeconds < 0) totalSeconds = 0;
16 |
17 | NSInteger minutes = floor(totalSeconds % 3600 / 60);
18 | NSInteger seconds = floor(totalSeconds % 60);
19 |
20 | NSString *formattedTime;
21 | if (totalSeconds > 3600)
22 | {
23 | NSInteger hours = floor(totalSeconds / 3600);
24 | formattedTime = [NSString stringWithFormat:@"%ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
25 | }
26 | else if (totalSeconds > 60)
27 | {
28 | formattedTime = [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
29 | }
30 | else
31 | {
32 | formattedTime = [NSString stringWithFormat:@"00:%02ld", (long)seconds];
33 | }
34 |
35 | return formattedTime;
36 | }
37 |
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/loudspeaker.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "loudspeaker"
3 | s.version = "1.0.0"
4 | s.summary = "An AVQueuePlayer-backed audio player with a modern, minimal UI"
5 | s.description = <<-DESC
6 | An audio player with a modern, minimal UI. Powered by AVQueuePlayer.
7 |
8 | * Plays all file formats supported by iOS
9 | * Auto Layout keeps it looking fiiiine however you jam it into your app
10 | * Jump forward or backward with gestures
11 | DESC
12 | s.homepage = "https://github.com/amco/loudspeaker"
13 | s.screenshot = "http://i.imgur.com/IOACIpO.png"
14 | s.license = 'MIT'
15 | s.author = { "Adam Yanalunas" => "adamy@yanalunas.com" }
16 | s.source = { :git => "https://github.com/amco/loudspeaker.git", :tag => s.version.to_s }
17 | s.social_media_url = 'https://twitter.com/adamyanalunas'
18 |
19 | s.platform = :ios, '9.0'
20 | s.requires_arc = true
21 |
22 | s.source_files = 'Pod/Classes'
23 | s.resource_bundles = {
24 | 'loudspeaker' => ['Pod/Assets/**/*']
25 | }
26 | s.dependency 'Masonry', '~> 1.1.0'
27 |
28 | s.public_header_files = 'Pod/Classes/**/*.h'
29 | s.frameworks = 'UIKit', 'AVFoundation'
30 | end
31 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPConfiguration.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPConfiguration.m
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 11/17/17.
6 | //
7 |
8 | #import "LSPConfiguration.h"
9 |
10 |
11 | @implementation LSPConfigurationBuilder
12 |
13 | + (LSPConfigurationBuilder *)defaultConfiguration
14 | {
15 | LSPConfigurationBuilder *config = LSPConfigurationBuilder.new;
16 | config.seekTolerance = CMTimeMake(1, 100);
17 | config.observationInterval = CMTimeMake(1, 35);
18 | config.volume = 1;
19 |
20 | return config;
21 | }
22 |
23 |
24 | + (LSPConfiguration *)configurationWithBuilder:(void (^)(LSPConfigurationBuilder *))builderBlock
25 | {
26 | LSPConfigurationBuilder *builder = LSPConfigurationBuilder.defaultConfiguration;
27 | builderBlock(builder);
28 |
29 | return [builder build];
30 | }
31 |
32 |
33 | - (LSPConfiguration *)build
34 | {
35 | return [LSPConfiguration.alloc initWithBuilder:self];
36 | }
37 |
38 | @end
39 |
40 |
41 | @implementation LSPConfiguration
42 |
43 | - (instancetype)initWithBuilder:(LSPConfigurationBuilder *)builder
44 | {
45 | self = [super init];
46 | if (!self) return nil;
47 |
48 | _seekTolerance = builder.seekTolerance;
49 | _observationInterval = builder.observationInterval;
50 | _volume = builder.volume;
51 |
52 | return self;
53 | }
54 |
55 | @end
56 |
--------------------------------------------------------------------------------
/Example/Tests/LSPCMTimeHelperSpec.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPCMTimeHelperSpec.m
3 | // Tests
4 | //
5 | // Created by Adam Yanalunas on 11/17/17.
6 | // Copyright © 2017 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 |
13 | SpecBegin(LSPCMTimeHelperSpec)
14 |
15 | describe(@"readableCMTime:", ^{
16 | it(@"formats a time hours long", ^{
17 | CMTime time = CMTimeMake(60 * 90, 1);
18 | NSString *formatted = [LSPCMTimeHelper readableCMTime:time];
19 |
20 | expect(formatted).to.equal(@"1:30:00");
21 | });
22 |
23 | it(@"formats a time minutes long", ^{
24 | CMTime time = CMTimeMake(60 * 45, 1);
25 | NSString *formatted = [LSPCMTimeHelper readableCMTime:time];
26 |
27 | expect(formatted).to.equal(@"45:00");
28 | });
29 |
30 | it(@"formats a time seconds long", ^{
31 | CMTime time = CMTimeMake(42, 1);
32 | NSString *formatted = [LSPCMTimeHelper readableCMTime:time];
33 |
34 | expect(formatted).to.equal(@"00:42");
35 | });
36 |
37 | it(@"ignores invalid time formats", ^{
38 | CMTime time = CMTimeMake(-1, 1);
39 | NSString *formatted = [LSPCMTimeHelper readableCMTime:time];
40 |
41 | expect(formatted).to.equal(@"00:00");
42 | });
43 | });
44 |
45 | SpecEnd
46 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPKit.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPKit.m
3 | //
4 | // Created by Adam Yanalunas on 8/29/14.
5 | //
6 | //
7 |
8 | #import "LSPKit.h"
9 |
10 |
11 | NSString *const LSPAudioPlayerStart = @"loudspeaker.audio.start";
12 | NSString *const LSPAudioPlayerStop = @"loudspeaker.audio.stop";
13 |
14 |
15 | @interface LSPKit ()
16 |
17 | + (nullable NSBundle *)loudspeakerBundle;
18 |
19 | @end
20 |
21 |
22 | @implementation LSPKit
23 |
24 |
25 | + (NSBundle *)loudspeakerBundle
26 | {
27 | return [NSBundle bundleWithURL:[[NSBundle bundleForClass:self.class] URLForResource:@"loudspeaker" withExtension:@"bundle"]];
28 | }
29 |
30 |
31 | + (UIImage *)imageNamed:(NSString *)name type:(NSString *)type
32 | {
33 | NSBundle *bundle = self.class.loudspeakerBundle;
34 | NSString *imageFileName = [NSString stringWithFormat:@"%@.%@", name, type];
35 |
36 | return [UIImage imageNamed:imageFileName inBundle:bundle compatibleWithTraitCollection:nil];
37 | }
38 |
39 | + (UIImage *)closeIcon
40 | {
41 | return [self.class imageNamed:@"audio_close_icon" type:@"png"];
42 | }
43 |
44 |
45 | + (NSString *)fontName
46 | {
47 | return @"Helvetica-Neue";
48 | }
49 |
50 |
51 | + (UIImage *)pauseIcon
52 | {
53 | return [self.class imageNamed:@"audio_pause_icon" type:@"png"];
54 | }
55 |
56 |
57 | + (UIImage *)playIcon
58 | {
59 | return [self.class imageNamed:@"audio_play_icon" type:@"png"];
60 | }
61 |
62 |
63 | + (NSString *)localizedString:(NSString *)string
64 | {
65 | return [self.class.loudspeakerBundle localizedStringForKey:string value:nil table:nil];
66 | }
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/Example/Tests/LSPConfigurationSpec.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPConfigurationSpec.m
3 | // Tests
4 | //
5 | // Created by Adam Yanalunas on 11/20/17.
6 | // Copyright © 2017 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 |
13 | SpecBegin(LSPConfigurationBuild)
14 |
15 |
16 | describe(@"defaultConfiguration", ^{
17 | __block LSPConfiguration *config;
18 |
19 | beforeEach(^{
20 | config = LSPConfigurationBuilder.defaultConfiguration;
21 | });
22 |
23 | it(@"contains the default configuration values", ^{
24 | expect(config.seekTolerance.value).to.equal(1);
25 | expect(config.seekTolerance.timescale).to.equal(100);
26 | expect(config.observationInterval.value).to.equal(1);
27 | expect(config.observationInterval.timescale).to.equal(35);
28 | expect(config.volume).to.equal(1);
29 | });
30 | });
31 |
32 | describe(@"configurationWithBuilder:", ^{
33 | __block LSPConfiguration *config;
34 |
35 | beforeEach(^{
36 | config = [LSPConfigurationBuilder configurationWithBuilder:^(LSPConfigurationBuilder *builder) {
37 | builder.seekTolerance = CMTimeMake(1, 2);
38 | builder.observationInterval = CMTimeMake(3, 4);
39 | builder.volume = .2;
40 | }];
41 | });
42 |
43 | it(@"uses the given values to build a configuration", ^{
44 | expect(config.seekTolerance.value).to.equal(1);
45 | expect(config.seekTolerance.timescale).to.equal(2);
46 | expect(config.observationInterval.value).to.equal(3);
47 | expect(config.observationInterval.timescale).to.equal(4);
48 | expect(config.volume).to.equal(.2);
49 | });
50 | });
51 |
52 |
53 | SpecEnd
54 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPConfiguration.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPConfiguration.h
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 11/17/17.
6 | //
7 |
8 | #import
9 | #import
10 |
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 |
15 | @class LSPConfiguration;
16 |
17 |
18 | @interface LSPConfigurationBuilder : NSObject
19 |
20 | /**
21 | How often the progress UI updates. Uses `CMTime` which has a format of interval/base.
22 | Defaults to 1/35th of a second, i.e. `CMTimeMake(1, 35)`.
23 | */
24 | @property (nonatomic) CMTime observationInterval;
25 |
26 | /**
27 | Defines accuracy of jump position when scrubbing or tapping the playback bar.
28 | The smaller the tolerance, the more accurate and more costly.
29 | Defaults to 1/100th of a second, i.e. `CMTimeMake(1, 100)`.
30 | */
31 | @property (nonatomic) CMTime seekTolerance;
32 |
33 | /**
34 | A 0-1 float for playback volume relative to system volume. Does not control
35 | volume of the OS.
36 | Defaults to 1
37 | */
38 | @property (nonatomic) CGFloat volume;
39 |
40 | + (nullable LSPConfiguration *)configurationWithBuilder:(void (^)(LSPConfigurationBuilder *builder))builderBlock;
41 | + (LSPConfigurationBuilder *)defaultConfiguration;
42 |
43 | - (nullable LSPConfiguration *)build;
44 |
45 | @end
46 |
47 |
48 | @interface LSPConfiguration : NSObject
49 |
50 | @property (nonatomic, readonly) CMTime observationInterval;
51 | @property (nonatomic, readonly) CMTime seekTolerance;
52 | @property (nonatomic, readonly) CGFloat volume;
53 |
54 |
55 | - (instancetype)init NS_UNAVAILABLE;
56 | + (instancetype)new NS_UNAVAILABLE;
57 |
58 | - (nullable instancetype)initWithBuilder:(LSPConfigurationBuilder *)builder NS_DESIGNATED_INITIALIZER;
59 |
60 | @end
61 |
62 |
63 | NS_ASSUME_NONNULL_END
64 |
--------------------------------------------------------------------------------
/Example/loudspeaker/loudspeaker-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | Launch
29 | UIMainStoryboardFile
30 | Main
31 | UIMainStoryboardFile~ipad
32 | Main
33 | UIRequiredDeviceCapabilities
34 |
35 | armv7
36 |
37 | UISupportedInterfaceOrientations
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationLandscapeLeft
41 | UIInterfaceOrientationLandscapeRight
42 |
43 | UISupportedInterfaceOrientations~ipad
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationPortraitUpsideDown
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # loudspeaker
2 |
3 | [](https://travis-ci.org/amco/loudspeaker)
4 | [](http://cocoadocs.org/docsets/loudspeaker)
5 | [](http://cocoadocs.org/docsets/loudspeaker)
6 | [](http://cocoadocs.org/docsets/loudspeaker)
7 |
8 | Loudspeaker is a simple, efficient, gorgeous, customizable audio player for iOS, backed by [AVQueuePlayer](https://developer.apple.com/library/ios/documentation/avfoundation/reference/avqueueplayer_class/Reference/Reference.html).
9 |
10 | 
11 |
12 | ## Installation
13 |
14 | loudspeaker is available through [CocoaPods](http://cocoapods.org). To install
15 | it, simply add the following line to your Podfile:
16 |
17 | pod "loudspeaker"
18 |
19 | ## Demo
20 |
21 | You can either run `pod try loudspeaker` from your command line or clone the repo and run `pod install` from the Example directory followed by loading up the Example workspace.
22 |
23 | ## Contribution
24 |
25 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
26 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
27 | * Fork the project and submit a pull request from a feature or bugfix branch.
28 | * Please include tests. This is important so we don't break your changes unintentionally in a future version.
29 | * Please don't modify the podspec, version, or changelog. If you do change these files, please isolate a separate commit so we can cherry-pick around it.
30 |
31 | ## Author
32 |
33 | Adam Yanalunas, adamy@amcoonline.net
34 |
35 | ## License
36 |
37 | loudspeaker is available under the MIT license. See the LICENSE file for more info.
38 |
39 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### 1.0.0
4 |
5 | This release contains breaking changes so read the changes carefully to see how you may be effected.
6 |
7 | ##### Added
8 |
9 | * 3x assets
10 | * Added iPhone X/safe area layout support
11 | * Added icons and loading screen to example project
12 | * Added configuration
13 | * Allows configuration of volume, interval to update progress bar and elapsed string, and accuracy for taps on progress to jump playback
14 | * All `LSPAudioViewController` instances must be initialized with a `LSPConfiguration` instance
15 | * A default configuration is available from `deafultConfiguration` in `LSPConfigurationBuilder` or you can build your own
16 | * Player frame is now determined by `LSPAudioViewController`'s frame
17 | * Added `show` and `hide:` to `LSPAudioViewController` to toggle the default presentation animations
18 | * Added accessibility to interactive elements
19 | * Accessibility elements are localized to English, localization strings available to be overridden by other languages
20 | * Added scrubbing gesture to progress bar
21 | * Added `jumpToProgress:` in `LSPAudioViewController` to move playback to an exact progress point
22 | * Added `setVolume:` in `LSPAudioPlayer` to set player volume during playback
23 | * More tests
24 |
25 | ##### Removed
26 |
27 | * Removed iOS 8 support
28 | * Removed static `sharedInstance` and `player` references from `LSPAudioPlayer`
29 | * Removed `bottomConstraint`, `observationInterval`, `seekTolerance`, `title`, and `URL` properties from `LSPAudioViewController`
30 | * These were moved into configurations or completely removed as they were unnecessary
31 |
32 | ##### Changed
33 |
34 | * Changed `audioViewController:didClosePlayer:` to be called after close animation is finished
35 | * `LSPAudioViewController` no longer overrides `view`; instead, the player UI is located in `playerView`
36 |
37 | ##### Fixed
38 |
39 | * Fixed timestamp not updating when playback paused but timeline tapped to change position
40 | * Fixed timestamp displaying inaccurate times when starting or stopping playback
41 |
42 | ### 0.2.0
43 |
44 | * Dropped iOS 7 support
45 | * Updated Masonry to 1.1
46 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (2.3.5)
5 | activesupport (4.2.10)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | claide (1.0.2)
11 | cocoapods (1.3.1)
12 | activesupport (>= 4.0.2, < 5)
13 | claide (>= 1.0.2, < 2.0)
14 | cocoapods-core (= 1.3.1)
15 | cocoapods-deintegrate (>= 1.0.1, < 2.0)
16 | cocoapods-downloader (>= 1.1.3, < 2.0)
17 | cocoapods-plugins (>= 1.0.0, < 2.0)
18 | cocoapods-search (>= 1.0.0, < 2.0)
19 | cocoapods-stats (>= 1.0.0, < 2.0)
20 | cocoapods-trunk (>= 1.2.0, < 2.0)
21 | cocoapods-try (>= 1.1.0, < 2.0)
22 | colored2 (~> 3.1)
23 | escape (~> 0.0.4)
24 | fourflusher (~> 2.0.1)
25 | gh_inspector (~> 1.0)
26 | molinillo (~> 0.5.7)
27 | nap (~> 1.0)
28 | ruby-macho (~> 1.1)
29 | xcodeproj (>= 1.5.1, < 2.0)
30 | cocoapods-core (1.3.1)
31 | activesupport (>= 4.0.2, < 6)
32 | fuzzy_match (~> 2.0.4)
33 | nap (~> 1.0)
34 | cocoapods-deintegrate (1.0.1)
35 | cocoapods-downloader (1.1.3)
36 | cocoapods-plugins (1.0.0)
37 | nap
38 | cocoapods-search (1.0.0)
39 | cocoapods-stats (1.0.0)
40 | cocoapods-trunk (1.3.0)
41 | nap (>= 0.8, < 2.0)
42 | netrc (~> 0.11)
43 | cocoapods-try (1.1.0)
44 | colored2 (3.1.2)
45 | concurrent-ruby (1.0.5)
46 | escape (0.0.4)
47 | fourflusher (2.0.1)
48 | fuzzy_match (2.0.4)
49 | gh_inspector (1.0.3)
50 | i18n (0.9.0)
51 | concurrent-ruby (~> 1.0)
52 | minitest (5.10.3)
53 | molinillo (0.5.7)
54 | nanaimo (0.2.3)
55 | nap (1.1.0)
56 | netrc (0.11.0)
57 | rouge (1.10.1)
58 | ruby-macho (1.1.0)
59 | thread_safe (0.3.6)
60 | tzinfo (1.2.4)
61 | thread_safe (~> 0.1)
62 | xcodeproj (1.5.3)
63 | CFPropertyList (~> 2.3.3)
64 | claide (>= 1.0.2, < 2.0)
65 | colored2 (~> 3.1)
66 | nanaimo (~> 0.2.3)
67 | xcpretty (0.2.2)
68 | rouge (~> 1.8)
69 |
70 | PLATFORMS
71 | ruby
72 |
73 | DEPENDENCIES
74 | cocoapods
75 | xcpretty
76 |
77 | BUNDLED WITH
78 | 1.15.4
79 |
--------------------------------------------------------------------------------
/Example/loudspeaker/LSPAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAppDelegate.m
3 | // loudspeaker
4 | //
5 | // Created by CocoaPods on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import "LSPAppDelegate.h"
10 |
11 | @implementation LSPAppDelegate
12 |
13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
14 | {
15 | // Override point for customization after application launch.
16 | return YES;
17 | }
18 |
19 | - (void)applicationWillResignActive:(UIApplication *)application
20 | {
21 | // 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.
22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
23 | }
24 |
25 | - (void)applicationDidEnterBackground:(UIApplication *)application
26 | {
27 | // 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.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | - (void)applicationWillEnterForeground:(UIApplication *)application
32 | {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | - (void)applicationDidBecomeActive:(UIApplication *)application
37 | {
38 | // 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.
39 | }
40 |
41 | - (void)applicationWillTerminate:(UIApplication *)application
42 | {
43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
44 | }
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/Example/loudspeaker/Images.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 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "60@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "60@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "idiom" : "ipad",
47 | "size" : "20x20",
48 | "scale" : "1x"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "size" : "20x20",
53 | "scale" : "2x"
54 | },
55 | {
56 | "idiom" : "ipad",
57 | "size" : "29x29",
58 | "scale" : "1x"
59 | },
60 | {
61 | "idiom" : "ipad",
62 | "size" : "29x29",
63 | "scale" : "2x"
64 | },
65 | {
66 | "idiom" : "ipad",
67 | "size" : "40x40",
68 | "scale" : "1x"
69 | },
70 | {
71 | "idiom" : "ipad",
72 | "size" : "40x40",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "76x76",
77 | "idiom" : "ipad",
78 | "filename" : "76@1x.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "76x76",
83 | "idiom" : "ipad",
84 | "filename" : "76@3x.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "83.5x83.5",
89 | "idiom" : "ipad",
90 | "filename" : "83.5@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "1024x1024",
95 | "idiom" : "ios-marketing",
96 | "filename" : "1024.png",
97 | "scale" : "1x"
98 | }
99 | ],
100 | "info" : {
101 | "version" : 1,
102 | "author" : "xcode"
103 | }
104 | }
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioViewController.h
3 | //
4 | // Created by Adam Yanalunas on 2/6/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import "LSPAudioView.h"
9 | #import "LSPAudioPlayerModel.h"
10 |
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 |
15 | @class LSPAudioPlayer, LSPAudioViewController, LSPConfiguration;
16 |
17 | @protocol LSPAudioViewControllerDelegate
18 |
19 | @optional
20 | - (void)audioViewController:(LSPAudioViewController *)viewController didClosePlayer:(LSPAudioPlayer *)player;
21 | - (void)audioViewController:(LSPAudioViewController *)viewController didPausePlayer:(LSPAudioPlayer *)player;
22 | - (void)audioViewController:(LSPAudioViewController *)viewController didPlayPlayer:(LSPAudioPlayer *)player;
23 | - (void)audioViewController:(LSPAudioViewController *)viewController didStopPlayer:(LSPAudioPlayer *)player;
24 |
25 | - (void)audioViewController:(LSPAudioViewController *)viewController willClosePlayer:(LSPAudioPlayer *)player;
26 | - (void)audioViewController:(LSPAudioViewController *)viewController willPausePlayer:(LSPAudioPlayer *)player;
27 | - (void)audioViewController:(LSPAudioViewController *)viewController willPlayPlayer:(LSPAudioPlayer *)player;
28 | - (void)audioViewController:(LSPAudioViewController *)viewController willStopPlayer:(LSPAudioPlayer *)player;
29 |
30 | @end
31 |
32 |
33 | @interface LSPAudioViewController : UIViewController
34 |
35 | @property (nonatomic, readonly) LSPConfiguration *configuration;
36 | @property (nonatomic, weak) id delegate;
37 | @property (nonatomic) LSPAudioPlayerModel *model;
38 | @property (nonatomic, readonly) LSPAudioView *playerView;
39 |
40 | + (instancetype)new NS_UNAVAILABLE;
41 | - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
42 | - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
43 | - (instancetype)initWithConfiguration:(LSPConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
44 |
45 | - (void)close;
46 | - (void)hide:(nullable void (^)(void))completion;
47 | - (BOOL)isPlaying;
48 | - (void)jumpToProgress:(Float64)progress;
49 | - (void)pause;
50 | - (void)play;
51 | - (void)playAudioWithURL:(NSURL *)url;
52 | - (void)reset;
53 | - (void)show;
54 | - (void)stop;
55 |
56 | @end
57 |
58 |
59 | NS_ASSUME_NONNULL_END
60 |
--------------------------------------------------------------------------------
/Example/loudspeaker/Launch.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Example/loudspeaker.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
78 |
79 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/Example/Tests/LSPAudioViewSpec.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioViewSpec.m
3 | // Tests
4 | //
5 | // Created by Adam Yanalunas on 11/20/17.
6 | // Copyright © 2017 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 |
13 | SpecBegin(LSPAudioView)
14 |
15 |
16 | describe(@"initForAutoLayout", ^{
17 | __block LSPAudioView *view;
18 |
19 | beforeEach(^{
20 | view = [LSPAudioView.alloc initForAutoLayout];
21 | });
22 |
23 | it(@"sets view to disable autoresize mask", ^{
24 | expect(view.translatesAutoresizingMaskIntoConstraints).to.beFalsy();
25 | });
26 |
27 | it(@"provides a default background color", ^{
28 | UIColor *bgColor = [UIColor colorWithWhite:238/255. alpha:1];
29 | expect(view.backgroundColor).to.equal(bgColor);
30 | });
31 |
32 | it(@"configures the view", ^{
33 | expect(view.closeButton.superview).to.beIdenticalTo(view);
34 | expect(view.playbackTimeLabel.superview).to.beIdenticalTo(view);
35 | expect(view.playPauseButton.superview).to.beIdenticalTo(view);
36 | expect(view.progressView.superview).to.beIdenticalTo(view);
37 | expect(view.titleLabel.superview).to.beIdenticalTo(view);
38 | });
39 | });
40 |
41 | describe(@"setCurrentProgress:forDuration:", ^{
42 | __block LSPAudioView *view;
43 |
44 | beforeEach(^{
45 | view = LSPAudioView.newAutoLayoutView;
46 | [view setCurrentProgress:@"123" forDuration:@"456"];
47 | });
48 |
49 | it(@"formats the progress text", ^{
50 | expect(view.playbackTimeLabel.text).to.equal(@"123 / 456");
51 | });
52 |
53 | it(@"applies an updated acessibility label", ^{
54 | expect(view.playbackTimeLabel.accessibilityLabel).to.equal(@"123 seconds elapsed");
55 | });
56 | });
57 |
58 | describe(@"showLayoutForPlaying:", ^{
59 | __block LSPAudioView *view;
60 |
61 | beforeEach(^{
62 | view = LSPAudioView.newAutoLayoutView;
63 | });
64 |
65 | context(@"play", ^{
66 | beforeEach(^{
67 | [view showLayoutForPlaying:NO];
68 | });
69 |
70 | it(@"shows the play button", ^{
71 | UIImage *actual = [view.playPauseButton imageForState:UIControlStateNormal];
72 | expect(actual).to.equal(LSPKit.playIcon);
73 | });
74 |
75 | it(@"applies accessibility information", ^{
76 | expect(view.playPauseButton.accessibilityLabel).to.equal(@"Play");
77 | expect(view.playPauseButton.accessibilityHint).to.equal(@"Tap to resume playback");
78 | });
79 | });
80 |
81 | context(@"pause", ^{
82 | beforeEach(^{
83 | [view showLayoutForPlaying:YES];
84 | });
85 |
86 | it(@"shows the pause button", ^{
87 | UIImage *actual = [view.playPauseButton imageForState:UIControlStateNormal];
88 | expect(actual).to.equal(LSPKit.pauseIcon);
89 | });
90 |
91 | it(@"applies accessibility information", ^{
92 | expect(view.playPauseButton.accessibilityLabel).to.equal(@"Pause");
93 | expect(view.playPauseButton.accessibilityHint).to.equal(@"Tap to pause playback");
94 | });
95 | });
96 | });
97 |
98 |
99 | SpecEnd
100 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPProgressView.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPProgressView.m
3 | //
4 | // Created by Adam Yanalunas on 2/12/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import "LSPProgressView.h"
9 | #import
10 |
11 |
12 | @interface LSPProgressView ()
13 |
14 | - (void)setupViews;
15 |
16 | @end
17 |
18 |
19 | @implementation LSPProgressView
20 |
21 |
22 | #pragma mark - Lifecycle
23 | - (instancetype)initForAutoLayout
24 | {
25 | self = [super initWithFrame:CGRectZero];
26 | if (!self) return nil;
27 |
28 | self.translatesAutoresizingMaskIntoConstraints = NO;
29 | [self setup];
30 |
31 | return self;
32 | }
33 |
34 |
35 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
36 | {
37 | return [self initForAutoLayout];
38 | }
39 |
40 |
41 | + (instancetype)newAutoLayoutView
42 | {
43 | LSPProgressView *view = [super new];
44 | if (!view) return nil;
45 |
46 | view.translatesAutoresizingMaskIntoConstraints = NO;
47 | [view setup];
48 |
49 | return view;
50 | }
51 |
52 |
53 | - (void)setup
54 | {
55 | [self setupViews];
56 | [self setForegroundColor:[UIColor colorWithRed:88/255. green:199/255. blue:226/255. alpha:1]];
57 | [self setBackgroundColor:[UIColor colorWithWhite:207/255. alpha:1]];
58 | }
59 |
60 |
61 | #pragma mark - Layout
62 | - (void)setupViews
63 | {
64 | [_progressBackground removeFromSuperview];
65 | [_progressBar removeFromSuperview];
66 |
67 | _progressBackground = nil;
68 | _progressBar = nil;
69 |
70 | [self addSubview:self.progressBackground];
71 | [self addSubview:self.progressBar];
72 |
73 | [self.progressBackground mas_makeConstraints:^(MASConstraintMaker *make) {
74 | make.edges.equalTo(self);
75 | }];
76 | }
77 |
78 |
79 | - (void)setProgress:(Float64)amount
80 | {
81 | [self.progressBackground layoutIfNeeded];
82 | CGRect updatedFrame = self.progressBackground.frame;
83 | updatedFrame.size.width = MIN(CGRectGetWidth(self.progressBackground.frame), CGRectGetWidth(updatedFrame) * amount);
84 | self.progressBar.frame = updatedFrame;
85 |
86 | self.progressBar.accessibilityLabel = [NSString stringWithFormat:@"%i%%", (int) round(updatedFrame.size.width / CGRectGetWidth(self.progressBackground.frame) * 100)];
87 | }
88 |
89 |
90 | #pragma mark - Properties
91 | - (void)setBackgroundColor:(UIColor *)backgroundColor
92 | {
93 | _backgroundColor = backgroundColor;
94 | _progressBackground.backgroundColor = _backgroundColor;
95 | }
96 |
97 |
98 | - (void)setForegroundColor:(UIColor *)foregroundColor
99 | {
100 | _foregroundColor = foregroundColor;
101 | _progressBar.backgroundColor = foregroundColor;
102 | }
103 |
104 |
105 | - (UIView *)progressBackground
106 | {
107 | if (!_progressBackground)
108 | {
109 | _progressBackground = UIView.new;
110 | _progressBackground.translatesAutoresizingMaskIntoConstraints = NO;
111 | }
112 |
113 | return _progressBackground;
114 | }
115 |
116 |
117 | - (UIView *)progressBar
118 | {
119 | if (!_progressBar)
120 | {
121 | _progressBar = UIView.new;
122 | _progressBar.translatesAutoresizingMaskIntoConstraints = NO;
123 |
124 | _progressBar.isAccessibilityElement = YES;
125 | _progressBar.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
126 | }
127 |
128 | return _progressBar;
129 | }
130 |
131 |
132 | @end
133 |
--------------------------------------------------------------------------------
/Example/loudspeaker/LSPViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPViewController.m
3 | // loudspeaker
4 | //
5 | // Created by Adam Yanalunas on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import "LSPViewController.h"
10 | #import
11 |
12 |
13 | @interface LSPViewController ()
14 |
15 | @property (nonatomic) LSPAudioViewController *audioVC;
16 | @property (nonatomic) LSPConfiguration *configuration;
17 |
18 | - (void)audioViewControllerSetup;
19 | - (void)configureDemo:(id)sender;
20 | - (void)playAudioWithURL:(NSURL *)audioURL;
21 |
22 | @end
23 |
24 |
25 | @implementation LSPViewController
26 |
27 |
28 | #pragma mark - Demo
29 | - (IBAction)launchDemo:(id)sender
30 | {
31 | self.demoButton.enabled = NO;
32 | self.harlequinButton.enabled = NO;
33 |
34 | [self configureDemo:sender];
35 |
36 | NSURL *audioURL = [[NSBundle mainBundle] URLForResource:@"Nyan-Cat" withExtension:@"mp3"];
37 | [self playAudioWithURL:audioURL];
38 | }
39 |
40 |
41 | - (void)resetDemoButtons
42 | {
43 | self.demoButton.enabled = YES;
44 | self.harlequinButton.enabled = YES;
45 | }
46 |
47 |
48 | - (IBAction)launchHarlequinDemo:(id)sender
49 | {
50 | [self launchDemo:sender];
51 | }
52 |
53 |
54 | - (void)configureDemo:(id)sender
55 | {
56 | if (sender == self.harlequinButton)
57 | {
58 | self.configuration = [LSPConfigurationBuilder configurationWithBuilder:^(LSPConfigurationBuilder *builder) {
59 | builder.volume = 0.1;
60 | }];
61 | self.audioVC = [LSPAudioViewController.alloc initWithConfiguration:self.configuration];
62 | CGFloat playerHeight = 120;
63 | self.audioVC.view.frame = CGRectMake(0, CGRectGetHeight(self.view.frame) - playerHeight, CGRectGetWidth(self.view.frame), playerHeight);
64 |
65 | self.audioVC.playerView.progressView.foregroundColor = UIColor.redColor;
66 | self.audioVC.playerView.progressView.backgroundColor = UIColor.blueColor;
67 | self.audioVC.playerView.backgroundColor = UIColor.greenColor;
68 | self.audioVC.playerView.playbackTimeLabel.textColor = UIColor.brownColor;
69 | self.audioVC.playerView.titleLabel.textColor = UIColor.yellowColor;
70 | }
71 | else
72 | {
73 | self.configuration = [LSPConfigurationBuilder.defaultConfiguration build];
74 | self.audioVC = [LSPAudioViewController.alloc initWithConfiguration:self.configuration];
75 | CGFloat playerHeight = 60;
76 | self.audioVC.view.frame = CGRectMake(0, CGRectGetHeight(self.view.frame) - playerHeight, CGRectGetWidth(self.view.frame), playerHeight);
77 |
78 | self.audioVC.playerView.progressView.foregroundColor = [UIColor colorWithRed:88/255. green:199/255. blue:226/255. alpha:1];
79 | self.audioVC.playerView.progressView.backgroundColor = [UIColor colorWithWhite:207/255. alpha:1];
80 | self.audioVC.playerView.backgroundColor = [UIColor colorWithWhite:238/255. alpha:1];
81 | self.audioVC.playerView.playbackTimeLabel.textColor = [UIColor colorWithWhite:102/255. alpha:1];
82 | self.audioVC.playerView.titleLabel.textColor = [UIColor colorWithWhite:102/255. alpha:1];
83 | }
84 | }
85 |
86 |
87 | #pragma mark - Player
88 | - (void)playAudioWithURL:(NSURL *)audioURL
89 | {
90 | [self audioViewControllerSetup];
91 | [self.audioVC playAudioWithURL:audioURL];
92 | [self.audioVC show];
93 | }
94 |
95 |
96 | - (void)audioViewControllerSetup
97 | {
98 | self.audioVC.delegate = self;
99 | [self.view addSubview:self.audioVC.view];
100 |
101 | [self.audioVC willMoveToParentViewController:self];
102 | [self addChildViewController:self.audioVC];
103 | [self.audioVC didMoveToParentViewController:self];
104 | }
105 |
106 |
107 | - (void)audioViewController:(LSPAudioViewController *)viewController didClosePlayer:(LSPAudioPlayer *)player
108 | {
109 | [self resetDemoButtons];
110 | }
111 |
112 |
113 | @end
114 |
--------------------------------------------------------------------------------
/Example/Tests/LSPProgressViewSpec.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPProgressViewSpec.m
3 | // Tests
4 | //
5 | // Created by Adam Yanalunas on 11/20/17.
6 | // Copyright © 2017 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 |
13 |
14 | SpecBegin(LSPProgressView)
15 |
16 |
17 | describe(@"initForAutoLayout", ^{
18 | __block LSPProgressView *view;
19 | __block OCMockObject *viewMock;
20 |
21 | beforeEach(^{
22 | view = [LSPProgressView.alloc initForAutoLayout];
23 | viewMock = OCMPartialMock(view);
24 | });
25 |
26 | afterEach(^{
27 | [viewMock stopMocking];
28 | });
29 |
30 | it(@"sets view to disable autoresize mask", ^{
31 | expect(view.translatesAutoresizingMaskIntoConstraints).to.beFalsy();
32 | });
33 |
34 | it(@"sets up the view", ^{
35 | [[viewMock expect] setup];
36 | });
37 | });
38 |
39 | describe(@"newAutoLayoutView", ^{
40 | __block LSPProgressView *view;
41 | __block OCMockObject *viewMock;
42 |
43 | beforeEach(^{
44 | view = LSPProgressView.newAutoLayoutView;
45 | viewMock = OCMPartialMock(view);
46 | });
47 |
48 | afterEach(^{
49 | [viewMock stopMocking];
50 | });
51 |
52 | it(@"sets view to disable autoresize mask", ^{
53 | expect(view.translatesAutoresizingMaskIntoConstraints).to.beFalsy();
54 | });
55 |
56 | it(@"sets up the view", ^{
57 | [[viewMock expect] setup];
58 | });
59 | });
60 |
61 | describe(@"setup", ^{
62 | __block LSPProgressView *view;
63 |
64 | beforeEach(^{
65 | view = LSPProgressView.newAutoLayoutView;
66 | [view setup];
67 | });
68 |
69 | it(@"sets up subviews", ^{
70 | expect(view.progressBackground.superview).to.beIdenticalTo(view);
71 | expect(view.progressBar.superview).to.beIdenticalTo(view);
72 | });
73 |
74 | it(@"provides default background colors", ^{
75 | UIColor *backgroundColor = [UIColor colorWithWhite:207/255. alpha:1];
76 | UIColor *foregroundColor = [UIColor colorWithRed:88/255. green:199/255. blue:226/255. alpha:1];
77 |
78 | expect(view.progressBackground.backgroundColor).to.equal(backgroundColor);
79 | expect(view.progressBar.backgroundColor).to.equal(foregroundColor);
80 | });
81 | });
82 |
83 | describe(@"setProgress:", ^{
84 | __block LSPProgressView *view;
85 |
86 | beforeEach(^{
87 | UIView *parent = [UIView.alloc initWithFrame:CGRectMake(0, 0, 400, 50)];
88 | view = LSPProgressView.newAutoLayoutView;
89 | [parent addSubview:view];
90 |
91 | NSDictionary *viewBindings = NSDictionaryOfVariableBindings(view);
92 | [parent addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:viewBindings]];
93 | [parent addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:viewBindings]];
94 |
95 | [view layoutIfNeeded];
96 |
97 | [view setProgress:.5];
98 | });
99 |
100 | it(@"moves the progress bar to the new progress amount", ^{
101 | CGFloat width = CGRectGetWidth(view.progressBar.frame);
102 | expect(width).to.equal(200);
103 | });
104 |
105 | it(@"updates the progress bar's accessiblity label", ^{
106 | expect(view.progressBar.accessibilityLabel).to.equal(@"50%");
107 | });
108 | });
109 |
110 | describe(@"progressBar", ^{
111 | __block LSPProgressView *view;
112 |
113 | beforeEach(^{
114 | view = LSPProgressView.newAutoLayoutView;
115 | });
116 |
117 | it(@"is configured to tell iOS its accessibility information updates frequently", ^{
118 | BOOL hasTraits = (view.progressBar.accessibilityTraits & UIAccessibilityTraitUpdatesFrequently) != 0;
119 | expect(hasTraits).to.beTruthy();
120 | });
121 | });
122 |
123 |
124 | SpecEnd
125 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioPlayer.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayer.m
3 | //
4 | // Created by Adam Yanalunas on 3/12/13.
5 | // Copyright (c) 2013 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import
9 | #import "LSPAudioPlayer.h"
10 |
11 | @interface LSPAudioPlayer ()
12 |
13 | @property (nonatomic) CMTime seekTolerance;
14 | @property (nonatomic) CMTime observationInterval;
15 |
16 | - (void)listenForEndOfAudio:(AVPlayerItem*)audio;
17 |
18 | @end
19 |
20 | @implementation LSPAudioPlayer
21 |
22 |
23 | - (instancetype)init
24 | {
25 | self = [super init];
26 | if (!self) return nil;
27 |
28 | _player = AVQueuePlayer.new;
29 | _observationInterval = CMTimeMake(1, 35);
30 | _seekTolerance = CMTimeMake(1, 100);
31 |
32 | return self;
33 | }
34 |
35 |
36 | #pragma mark - Playback
37 | - (void)playFromURLString:(NSString*)urlString
38 | {
39 | NSString *tildeString = [urlString stringByExpandingTildeInPath];
40 | NSURL *url = [NSURL fileURLWithPath:[tildeString stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet] isDirectory:NO];
41 | [self playFromURL:url];
42 | }
43 |
44 | - (void)playFromURL:(NSURL*)path
45 | {
46 | if (![path.absoluteString isEqualToString:self.previousPath.absoluteString])
47 | {
48 | [self pause];
49 | [self.player removeAllItems];
50 |
51 | AVPlayerItem *audioItem = [AVPlayerItem playerItemWithURL:path];
52 |
53 | [self listenForEndOfAudio:audioItem];
54 | [self.player insertItem:audioItem afterItem:nil];
55 |
56 | self.previousPath = path;
57 | }
58 |
59 | [self togglePlayback];
60 | }
61 |
62 |
63 | - (void)play
64 | {
65 | [self.player play];
66 | }
67 |
68 |
69 | - (void)pause
70 | {
71 | [self.player pause];
72 | }
73 |
74 |
75 | - (void)stop
76 | {
77 | [self pause];
78 | [self.player removeAllItems];
79 | self.previousPath = nil;
80 | }
81 |
82 |
83 | - (void)setVolume:(float)volume
84 | {
85 | self.player.volume = volume;
86 | }
87 |
88 |
89 | - (void)jumpToProgress:(Float64)progress
90 | {
91 | CMTime time = [self timeFromProgress:progress];
92 | [self.player seekToTime:time toleranceBefore:self.seekTolerance toleranceAfter:self.seekTolerance];
93 | }
94 |
95 |
96 | - (void)togglePlayback
97 | {
98 | if (self.isPlaying)
99 | {
100 | [self pause];
101 | }
102 | else
103 | {
104 | [self play];
105 | }
106 | }
107 |
108 |
109 | - (id)addProgressObserver:(void (^)(CMTime time))observer
110 | {
111 | return [self.player addPeriodicTimeObserverForInterval:self.observationInterval queue:NULL usingBlock:^(CMTime time) {
112 | observer(time);
113 | }];
114 | }
115 |
116 |
117 | - (void)removeProgressObserver:(id)observer
118 | {
119 | [self.player removeTimeObserver:observer];
120 | }
121 |
122 |
123 | #pragma mark - Helpers
124 | - (CMTime)currentTime
125 | {
126 | return self.player.currentItem.currentTime;
127 | }
128 |
129 |
130 | - (CMTime)duration
131 | {
132 | return self.player.currentItem.duration;
133 | }
134 |
135 |
136 | - (AVPlayerStatus)status
137 | {
138 | return self.player.status;
139 | }
140 |
141 |
142 | - (BOOL)isPlaying
143 | {
144 | return (self.player && self.player.rate);
145 | }
146 |
147 |
148 | - (CMTime)timeFromProgress:(Float64)progress
149 | {
150 | return CMTimeMakeWithSeconds(CMTimeGetSeconds(self.duration) * progress, 100);
151 | }
152 |
153 |
154 | - (void)listenForEndOfAudio:(AVPlayerItem*)audio
155 | {
156 | NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
157 | [center removeObserver:self];
158 | [center addObserver:self selector:@selector(resetPlayer) name:AVPlayerItemDidPlayToEndTimeNotification object:audio];
159 | }
160 |
161 | - (void)resetPlayer
162 | {
163 | if (self.previousPath)
164 | {
165 | [NSNotificationCenter.defaultCenter removeObserver:self];
166 | [self.player removeAllItems];
167 | self.previousPath = nil;
168 | }
169 | }
170 |
171 | @end
172 |
--------------------------------------------------------------------------------
/Example/loudspeaker.xcodeproj/xcshareddata/xcschemes/loudspeaker.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
87 |
93 |
94 |
95 |
96 |
98 |
99 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/Example/loudspeaker/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 |
30 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioView.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayer.m
3 | //
4 | // Created by Adam Yanalunas on 2/6/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import "LSPAudioView.h"
9 | #import "LSPKit.h"
10 | #import "LSPProgressView.h"
11 | #import
12 |
13 |
14 | @interface LSPAudioView ()
15 |
16 | - (void)applyConstraints;
17 | - (void)attachSubviews;
18 | - (void)reset;
19 | - (void)setup;
20 |
21 | @end
22 |
23 |
24 | @implementation LSPAudioView
25 |
26 |
27 | #pragma mark - Lifespan
28 | - (instancetype)initForAutoLayout
29 | {
30 | self = [super initWithFrame:CGRectZero];
31 | if (!self) return nil;
32 |
33 | self.translatesAutoresizingMaskIntoConstraints = NO;
34 | [self setup];
35 |
36 | return self;
37 | }
38 |
39 |
40 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
41 | {
42 | return [self initForAutoLayout];
43 | }
44 |
45 |
46 | + (instancetype)newAutoLayoutView
47 | {
48 | LSPAudioView *view = [super new];
49 | if (!view) return nil;
50 |
51 | view.translatesAutoresizingMaskIntoConstraints = NO;
52 | [view setup];
53 |
54 | return view;
55 | }
56 |
57 |
58 | - (void)setup
59 | {
60 | [self reset];
61 |
62 | self.backgroundColor = [UIColor colorWithWhite:238/255. alpha:1];
63 |
64 | [self attachSubviews];
65 | [self applyConstraints];
66 | [self showLayoutForPlaying:NO];
67 | }
68 |
69 |
70 | - (void)reset
71 | {
72 | [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
73 | }
74 |
75 |
76 | #pragma mark - Layout
77 | - (void)applyConstraints
78 | {
79 | float horizontalPadding = 12.f;
80 | float verticalPadding = 12.f;
81 |
82 | [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
83 | make.left.equalTo(_playPauseButton.mas_right).offset(horizontalPadding);
84 | make.right.greaterThanOrEqualTo(_closeButton.mas_left).offset(-horizontalPadding);
85 | make.top.equalTo(_titleLabel.mas_bottom).offset(6);
86 | make.height.equalTo(@9);
87 | }];
88 |
89 | [_playPauseButton mas_makeConstraints:^(MASConstraintMaker *make) {
90 | make.left.equalTo(self.mas_left);
91 | make.width.and.height.equalTo(@60);
92 | make.centerY.equalTo(self.mas_centerY);
93 | }];
94 |
95 | [_closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
96 | make.centerY.equalTo(self.mas_centerY);
97 | make.right.equalTo(self.mas_right);
98 | make.width.and.height.equalTo(@60);
99 | }];
100 |
101 | [_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
102 | make.height.equalTo(@20);
103 | make.top.equalTo(self.mas_top).offset(verticalPadding);
104 | make.right.greaterThanOrEqualTo(_playbackTimeLabel.mas_left).offset(horizontalPadding);
105 | make.left.equalTo(_playPauseButton.mas_right).offset(horizontalPadding);
106 | }];
107 |
108 | [_playbackTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
109 | make.width.equalTo(@100);
110 | make.right.greaterThanOrEqualTo(_closeButton.mas_left).offset(-horizontalPadding);
111 | make.top.greaterThanOrEqualTo(self.mas_top).offset(verticalPadding);
112 | }];
113 | }
114 |
115 |
116 | - (void)attachSubviews
117 | {
118 | [self addSubview:self.closeButton];
119 | [self addSubview:self.playbackTimeLabel];
120 | [self addSubview:self.progressView];
121 | [self addSubview:self.titleLabel];
122 | [self addSubview:self.playPauseButton];
123 | }
124 |
125 |
126 | #pragma mark - Playback information
127 | - (void)showLayoutForPlaying:(BOOL)isPlaying
128 | {
129 | UIImage *icon = (isPlaying ? LSPKit.pauseIcon : LSPKit.playIcon);
130 | [_playPauseButton setImage:icon forState:UIControlStateNormal];
131 | [self setAccessibilityForPlayback:isPlaying];
132 | }
133 |
134 |
135 | - (void)setCurrentProgress:(NSString *)progress forDuration:(NSString *)duration
136 | {
137 | NSString *labelText = [NSString stringWithFormat:@"%@ / %@", progress, duration];
138 |
139 | self.playbackTimeLabel.text = labelText;
140 | self.playbackTimeLabel.accessibilityLabel = [NSString stringWithFormat:[LSPKit localizedString:@"loudspeaker.seconds_elapsed_format"], progress];
141 | }
142 |
143 |
144 | #pragma mark - Helpers
145 | - (void)setAccessibilityForPlayback:(BOOL)isPlaying
146 | {
147 | if (isPlaying)
148 | {
149 | _playPauseButton.accessibilityLabel = [LSPKit localizedString:@"loudspeaker.pause"];
150 | _playPauseButton.accessibilityHint = [LSPKit localizedString:@"loudspeaker.tap_to_pause_playback"];
151 | }
152 | else
153 | {
154 | _playPauseButton.accessibilityLabel = [LSPKit localizedString:@"loudspeaker.play"];
155 | _playPauseButton.accessibilityHint = [LSPKit localizedString:@"loudspeaker.tap_to_resume_playback"];
156 | }
157 | }
158 |
159 |
160 | #pragma mark - Properties
161 | - (UIButton *)closeButton
162 | {
163 | if (!_closeButton)
164 | {
165 | _closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
166 | _closeButton.translatesAutoresizingMaskIntoConstraints = NO;
167 | [_closeButton setImage:LSPKit.closeIcon forState:UIControlStateNormal];
168 |
169 | _closeButton.isAccessibilityElement = YES;
170 | _closeButton.accessibilityLabel = [LSPKit localizedString:@"loudspeaker.close"];
171 | }
172 |
173 | return _closeButton;
174 | }
175 |
176 |
177 | - (UILabel *)playbackTimeLabel
178 | {
179 | if (!_playbackTimeLabel)
180 | {
181 | _playbackTimeLabel = [UILabel.alloc initWithFrame:CGRectZero];
182 | _playbackTimeLabel.adjustsFontSizeToFitWidth = YES;
183 | _playbackTimeLabel.font = [UIFont fontWithName:LSPKit.fontName size:14.];
184 | _playbackTimeLabel.textColor = [UIColor colorWithWhite:102/255. alpha:1];
185 | _playbackTimeLabel.textAlignment = NSTextAlignmentRight;
186 | _playbackTimeLabel.translatesAutoresizingMaskIntoConstraints = NO;
187 |
188 | _playbackTimeLabel.isAccessibilityElement = YES;
189 | _playbackTimeLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
190 | }
191 |
192 | return _playbackTimeLabel;
193 | }
194 |
195 |
196 | - (UIButton *)playPauseButton
197 | {
198 | if (!_playPauseButton)
199 | {
200 | _playPauseButton = [UIButton buttonWithType:UIButtonTypeCustom];
201 | _playPauseButton.translatesAutoresizingMaskIntoConstraints = NO;
202 | [_playPauseButton setImage:LSPKit.playIcon forState:UIControlStateNormal];
203 |
204 | _playPauseButton.isAccessibilityElement = YES;
205 | [self setAccessibilityForPlayback:NO];
206 | }
207 |
208 | return _playPauseButton;
209 | }
210 |
211 |
212 | - (LSPProgressView *)progressView
213 | {
214 | if (!_progressView)
215 | {
216 | _progressView = [LSPProgressView newAutoLayoutView];
217 | }
218 |
219 | return _progressView;
220 | }
221 |
222 |
223 | - (UILabel *)titleLabel
224 | {
225 | if (!_titleLabel)
226 | {
227 | _titleLabel = [UILabel.alloc initWithFrame:CGRectZero];
228 | _titleLabel.font = [UIFont fontWithName:LSPKit.fontName size:14.];
229 | _titleLabel.textAlignment = NSTextAlignmentLeft;
230 | _titleLabel.textColor = [UIColor colorWithWhite:102/255. alpha:1];
231 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
232 |
233 | _titleLabel.isAccessibilityElement = YES;
234 | }
235 |
236 | return _titleLabel;
237 | }
238 |
239 | @end
240 |
--------------------------------------------------------------------------------
/Example/Tests/LSPAudioPlayerSpec.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioPlayerSpec.m
3 | // LSPAudioPlayerSpec
4 | //
5 | // Created by Adam Yanalunas on 08/29/2014.
6 | // Copyright (c) 2014 Adam Yanalunas. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 | #import
13 |
14 |
15 | #define SETUP_AND_RESET_PLAYER \
16 | __block LSPAudioPlayer* player = LSPAudioPlayer.new;\
17 | \
18 | afterEach(^{\
19 | [player resetPlayer];\
20 | });\
21 | \
22 | afterAll(^{\
23 | player = nil;\
24 | });
25 |
26 |
27 | SpecBegin(LSPAudioPlayerSpec)
28 |
29 | context(@"LSPAudioPlayer", ^{
30 | __block NSURL* audioFile1 = nil;
31 | __block NSURL* audioFile2 = nil;
32 |
33 | beforeAll(^{
34 | audioFile1 = [NSBundle.mainBundle URLForResource:@"Nyan-Cat" withExtension:@"mp3"];
35 | audioFile2 = [[NSBundle bundleForClass:self.class] URLForResource:@"Nyan-Cat-Test" withExtension:@"mp3"];
36 | });
37 |
38 | describe(@"playFromURLString:", ^{
39 | __block LSPAudioPlayer* player = LSPAudioPlayer.new;
40 | __block OCMockObject *playerMock;
41 | __block NSString* file = @"~/Documents/banana.mp3";
42 |
43 | beforeEach(^{
44 | playerMock = OCMPartialMock(player);
45 | });
46 |
47 | afterEach(^{
48 | [playerMock stopMocking];
49 | [player resetPlayer];
50 | });
51 |
52 | afterAll(^{
53 | player = nil;
54 | });
55 |
56 | it(@"transforms a URL-like string into a NSURL, calling configureWithURL", ^{
57 | NSString* tildeString = [file stringByExpandingTildeInPath];
58 | NSURL* expectedUrl = [NSURL fileURLWithPath:[tildeString stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet] isDirectory:NO];
59 |
60 | [[playerMock expect] playFromURL:expectedUrl];
61 |
62 | [player playFromURLString:file];
63 | });
64 | });
65 |
66 | describe(@"playFromURL:", ^{
67 | __block LSPAudioPlayer* player = LSPAudioPlayer.new;
68 |
69 | afterEach(^{
70 | [player resetPlayer];
71 | });
72 |
73 | afterAll(^{
74 | player = nil;
75 | });
76 |
77 | it(@"immediately starts playing file if it is not the previously-called file", ^{
78 | AVQueuePlayer* queuePlayer = player.player;
79 |
80 | expect(queuePlayer.items).to.beEmpty();
81 |
82 | [player playFromURL:audioFile1];
83 |
84 | expect(queuePlayer.items).to.haveCountOf(1);
85 | expect([(AVURLAsset *)[queuePlayer.items[0] asset] URL]).to.equal(audioFile1);
86 | });
87 |
88 | it(@"doesn't create a new item when called again with the previous file", ^{
89 | AVQueuePlayer* queuePlayer = player.player;
90 |
91 | [player playFromURL:audioFile1];
92 | expect(queuePlayer.items).to.haveCountOf(1);
93 |
94 | [player playFromURL:audioFile1];
95 | expect(queuePlayer.items).to.haveCountOf(1);
96 | });
97 |
98 | it(@"should toggle playback", ^{
99 | [player playFromURL:audioFile1];
100 |
101 | expect(player.isPlaying).to.beTruthy();
102 | });
103 |
104 | it(@"sets up a new file to play if it is different than the last", ^{
105 | AVQueuePlayer* queuePlayer = player.player;
106 |
107 | [player playFromURL:audioFile1];
108 | NSURL* oldPath = player.previousPath;
109 |
110 | [queuePlayer pause];
111 |
112 | [player playFromURL:audioFile2];
113 | NSURL* newPath = audioFile2;
114 |
115 | AVPlayerItem* track = [queuePlayer.items objectAtIndex:0];
116 | expect(track.asset.tracks).to.haveCountOf(1);
117 | expect(player.previousPath).to.equal(newPath);
118 | expect(player.previousPath).toNot.equal(oldPath);
119 | });
120 | });
121 |
122 |
123 | describe(@"play", ^{
124 | SETUP_AND_RESET_PLAYER
125 |
126 | it(@"should play the current track", ^{
127 | [player play];
128 | expect(player.isPlaying).to.beTruthy();
129 | });
130 | });
131 |
132 |
133 | describe(@"pause", ^{
134 | SETUP_AND_RESET_PLAYER
135 |
136 | it(@"should pause the current track", ^{
137 | [player pause];
138 | expect(player.isPlaying).to.beFalsy();
139 | });
140 | });
141 |
142 |
143 | describe(@"stop", ^{
144 | SETUP_AND_RESET_PLAYER
145 |
146 | __block AVQueuePlayer* queuePlayer = player.player;
147 |
148 | beforeAll(^{
149 | queuePlayer = player.player;
150 | });
151 |
152 | beforeEach(^{
153 | [player playFromURL:audioFile1];
154 | });
155 |
156 | afterAll(^{
157 | queuePlayer = nil;
158 | });
159 |
160 | it(@"should remove any queued items", ^{
161 | expect(queuePlayer.items).to.haveCountOf(1);
162 |
163 | [player stop];
164 |
165 | expect(queuePlayer.items).to.haveCountOf(0);
166 | });
167 |
168 | it(@"should forget the last track played", ^{
169 | expect(player.previousPath).to.equal(audioFile1);
170 |
171 | [player stop];
172 |
173 | expect(player.previousPath).to.beNil();
174 | });
175 | });
176 |
177 |
178 | describe(@"togglePlayback", ^{
179 | SETUP_AND_RESET_PLAYER
180 |
181 | it(@"should pause playback if currently playing", ^{
182 | [player pause];
183 | [player togglePlayback];
184 | expect(player.isPlaying).to.beTruthy();
185 | });
186 |
187 | it(@"should resume playback if currently paused", ^{
188 | [player play];
189 | [player togglePlayback];
190 | expect(player.isPlaying).to.beFalsy();
191 | });
192 | });
193 |
194 |
195 | describe(@"currentTime", ^{
196 | __block CMTime destinationTime;
197 |
198 | SETUP_AND_RESET_PLAYER
199 |
200 | beforeEach(^{
201 | [player playFromURL:audioFile1];
202 |
203 | AVQueuePlayer *queuePlayer = player.player;
204 | destinationTime = [player timeFromProgress:.5];
205 | CMTime seekTolerance = CMTimeMake(1, 100);
206 | [queuePlayer seekToTime:destinationTime toleranceBefore:seekTolerance toleranceAfter:seekTolerance];
207 | });
208 |
209 | it(@"should return the elapsed time of the current track", ^{
210 | expect(CMTIME_COMPARE_INLINE(player.currentTime, ==, destinationTime)).to.beTruthy();
211 | });
212 | });
213 |
214 |
215 | describe(@"duration", ^{
216 | __block CMTime durationTime;
217 |
218 | SETUP_AND_RESET_PLAYER
219 |
220 | beforeEach(^{
221 | [player playFromURL:audioFile1];
222 | AVQueuePlayer *queuePlayer = player.player;
223 | durationTime = queuePlayer.currentItem.duration;
224 | });
225 |
226 | it(@"should return the duration of the current track", ^{
227 | expect(CMTIME_COMPARE_INLINE(player.duration, ==, durationTime)).to.beTruthy();
228 | });
229 | });
230 |
231 |
232 | describe(@"status", ^{
233 | SETUP_AND_RESET_PLAYER
234 |
235 | beforeEach(^{
236 | [player playFromURL:audioFile1];
237 | });
238 |
239 | it(@"should return the player's current playback status", ^{
240 | AVQueuePlayer *queuePlayer = player.player;
241 | expect(player.status).to.equal(queuePlayer.status);
242 | });
243 | });
244 |
245 |
246 | describe(@"isPlaying", ^{
247 | SETUP_AND_RESET_PLAYER
248 |
249 | beforeEach(^{
250 | [player playFromURL:audioFile1];
251 | });
252 |
253 | it(@"should return YES if audio is determined to be playing", ^{
254 | [player play];
255 | expect(player.isPlaying).to.beTruthy();
256 | });
257 |
258 | it(@"should return NO if audio is determined to be paused or stopped", ^{
259 | [player pause];
260 | expect(player.isPlaying).to.beFalsy();
261 |
262 | [player play];
263 | expect(player.isPlaying).to.beTruthy();
264 |
265 | [player stop];
266 | expect(player.isPlaying).to.beFalsy();
267 | });
268 | });
269 |
270 |
271 | describe(@"timeForProgress:", ^{
272 | SETUP_AND_RESET_PLAYER
273 |
274 | beforeEach(^{
275 | // 30 seconds
276 | CMTime duration = CMTimeMake(30, 1);
277 | player = OCMPartialMock(player);
278 | OCMStub(player.duration).andReturn(duration);
279 | });
280 |
281 | afterEach(^{
282 | [(OCMockObject *)player stopMocking];
283 | });
284 |
285 | it(@"should return a time for the current track based on a 0-1 progress through the track", ^{
286 | CMTime halfway = [player timeFromProgress:.5];
287 | Float64 halfwaySeconds = CMTimeGetSeconds(halfway);
288 | expect(halfwaySeconds).to.equal(15.);
289 |
290 | CMTime threeQuarters = [player timeFromProgress:.75];
291 | Float64 threeQuartersSeconds = CMTimeGetSeconds(threeQuarters);
292 | expect(threeQuartersSeconds).to.equal(22.5);
293 | });
294 | });
295 |
296 |
297 | describe(@"resetPlayer", ^{
298 | __block OCMockObject *ncStub;
299 | __block LSPAudioPlayer* player = LSPAudioPlayer.new;
300 |
301 | beforeEach(^{
302 | ncStub = OCMPartialMock(NSNotificationCenter.defaultCenter);
303 | });
304 |
305 | afterAll(^{
306 | player = nil;
307 | });
308 |
309 | afterEach(^{
310 | [player resetPlayer];
311 | [ncStub stopMocking];
312 | });
313 |
314 | it(@"removes observers if previousPath is not nil", ^{
315 | player.previousPath = audioFile1;
316 |
317 | [[ncStub expect] removeObserver:player];
318 | [player resetPlayer];
319 | [ncStub verify];
320 | });
321 |
322 | it(@"doesn't remove observers if previousPath is nil", ^{
323 | [[ncStub reject] removeObserver:player];
324 | [player resetPlayer];
325 | [ncStub verify];
326 | });
327 |
328 | it(@"removes items from queue if previousPath is not nil", ^{
329 | player.previousPath = audioFile1;
330 |
331 | AVPlayerItem* item = [AVPlayerItem playerItemWithURL:audioFile1];
332 |
333 | AVQueuePlayer* queuePlayer = player.player;
334 | [queuePlayer insertItem:item afterItem:nil];
335 |
336 | expect(queuePlayer.items).to.haveCountOf(1);
337 | [player resetPlayer];
338 | expect(queuePlayer.items).to.haveCountOf(0);
339 | });
340 |
341 | // TODO: Check that this is the test causing Travis to fail tests
342 | // it(@"does not remove items from queue if previousPath is nil", ^{
343 | // player.previousPath = audioFile1;
344 | //
345 | // AVPlayerItem* item = [AVPlayerItem playerItemWithURL:audioFile1];
346 | //
347 | // AVQueuePlayer* queuePlayer = LSPAudioPlayer.player;
348 | // [queuePlayer insertItem:item afterItem:nil];
349 | // [queuePlayer pause];
350 | //
351 | // expect(queuePlayer.items).to.haveCountOf(1);
352 | // player.previousPath = nil;
353 | // [player resetPlayer];
354 | // expect(queuePlayer.items).to.haveCountOf(1);
355 | // });
356 |
357 | it(@"nils out the previousPath", ^{
358 | player.previousPath = [NSURL URLWithString:@"http://foo.com"];
359 |
360 | [player resetPlayer];
361 |
362 | expect(player.previousPath).to.beNil();
363 | });
364 | });
365 | });
366 |
367 |
368 | SpecEnd
369 |
--------------------------------------------------------------------------------
/Pod/Classes/LSPAudioViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // LSPAudioViewController.m
3 | //
4 | // Created by Adam Yanalunas on 2/6/14.
5 | // Copyright (c) 2014 Amco International Education Services, LLC. All rights reserved.
6 | //
7 |
8 | #import "LSPAudioPlayer.h"
9 | #import "LSPAudioView.h"
10 | #import "LSPAudioViewController.h"
11 | #import "LSPCMTimeHelper.h"
12 | #import "LSPConfiguration.h"
13 | #import "LSPKit.h"
14 | #import "LSPProgressView.h"
15 | #import
16 |
17 |
18 | static void * LSPAudioViewControllerContext = &LSPAudioViewControllerContext;
19 |
20 |
21 | @interface LSPAudioViewController ()
22 |
23 | @property (nonatomic) LSPAudioPlayer *player;
24 | @property (nonatomic) MASConstraint *bottomConstraint;
25 | @property (nonatomic) LSPConfiguration *configuration;
26 | @property (nonatomic) LSPAudioView *playerView;
27 | @property (nonatomic) BOOL playing;
28 | @property (nonatomic) id progressObserver;
29 |
30 | - (void)addTimeObserver;
31 | - (MASViewAttribute *)bottomLayout;
32 | - (CGFloat)bottomOffset;
33 | - (void)closeButtonPressed:(UIButton *)button;
34 | - (void)handleTimelineTap:(UITapGestureRecognizer *)gesture;
35 | - (void)playedUntilEnd:(NSNotification *)notificaiton;
36 | - (Float64)progressFromGesture:(UIGestureRecognizer *)gesture;
37 | - (void)removeTimeObserver;
38 | - (void)showProgress;
39 | - (void)togglePlayPause:(UIButton *)button;
40 | - (void)updateProgress;
41 | - (void)updateTimeLabel;
42 |
43 | @end
44 |
45 |
46 | @implementation LSPAudioViewController
47 |
48 | @dynamic view;
49 |
50 | #pragma mark - Lifecycle
51 | - (instancetype)initWithConfiguration:(LSPConfiguration *)configuration
52 | {
53 | self = [super initWithNibName:nil bundle:nil];
54 | if (!self) return nil;
55 |
56 | _configuration = configuration;
57 | _model = LSPAudioPlayerModel.new;
58 | _player = LSPAudioPlayer.new;
59 | _playerView = [LSPAudioView newAutoLayoutView];
60 | [_player setVolume:_configuration.volume];
61 |
62 | return self;
63 | }
64 |
65 |
66 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
67 | {
68 | return nil;
69 | }
70 |
71 |
72 | - (void)viewDidLoad
73 | {
74 | [super viewDidLoad];
75 |
76 | [self.view addSubview:self.playerView];
77 | [self assignConstraintsToView];
78 |
79 | [self.playerView.closeButton addTarget:self action:@selector(closeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
80 | [self.playerView.playPauseButton addTarget:self action:@selector(togglePlayPause:) forControlEvents:UIControlEventTouchUpInside];
81 |
82 | UITapGestureRecognizer *tapGesture = [UITapGestureRecognizer.alloc initWithTarget:self action:@selector(handleTimelineTap:)];
83 | [self.playerView.progressView addGestureRecognizer:tapGesture];
84 |
85 | UIPanGestureRecognizer *panGesture = [UIPanGestureRecognizer.alloc initWithTarget:self action:@selector(handleTimelinePan:)];
86 | [self.playerView.progressView addGestureRecognizer:panGesture];
87 |
88 | [self addObserver:self forKeyPath:@"playing" options:0 context:&LSPAudioViewControllerContext];
89 | }
90 |
91 |
92 | - (void)reset
93 | {
94 | [self stop];
95 | [self removeTimeObserver];
96 |
97 | @try
98 | {
99 | [self removeObserver:self forKeyPath:@"playing" context:&LSPAudioViewControllerContext];
100 | }
101 | @catch (NSException * __unused exception) {}
102 |
103 | [[NSNotificationCenter defaultCenter] removeObserver:self];
104 |
105 | if (self.view.superview)
106 | {
107 | [self.view removeFromSuperview];
108 | }
109 |
110 | if (self.parentViewController)
111 | {
112 | [self removeFromParentViewController];
113 | }
114 |
115 | _delegate = nil;
116 | _playing = NO;
117 | }
118 |
119 |
120 | #pragma mark - KVO
121 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
122 | {
123 | if (context == LSPAudioViewControllerContext)
124 | {
125 | if ([keyPath isEqualToString:NSStringFromSelector(@selector(playing))])
126 | {
127 | [self.playerView showLayoutForPlaying:self.player.isPlaying];
128 | }
129 | }
130 | }
131 |
132 |
133 | #pragma mark - Layout
134 | - (void)assignConstraintsToView
135 | {
136 | [self.playerView mas_makeConstraints:^(MASConstraintMaker *make) {
137 | make.left.and.right.equalTo(self.view);
138 | make.height.equalTo(self.view);
139 | make.bottom.equalTo(self.bottomLayout);
140 | }];
141 | [self.view layoutIfNeeded];
142 | }
143 |
144 |
145 | - (void)show
146 | {
147 | CGFloat offset = [self bottomOffset];
148 | [self.playerView mas_updateConstraints:^(MASConstraintMaker *make) {
149 | make.bottom.equalTo(self.bottomLayout).with.offset(offset);
150 | }];
151 | [self.view layoutIfNeeded];
152 |
153 | NSTimeInterval duration = .666f;
154 | NSTimeInterval delay = 0;
155 | UIViewAnimationOptions options = (UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut);
156 |
157 | [UIView animateWithDuration:duration delay:delay options:options animations:^{
158 | [self.playerView mas_updateConstraints:^(MASConstraintMaker *make) {
159 | make.bottom.equalTo(self.bottomLayout).with.offset(0);
160 | }];
161 | [self.view layoutIfNeeded];
162 | } completion:nil];
163 | }
164 |
165 |
166 | - (void)hide:(void (^)(void))completion
167 | {
168 | [self.view layoutIfNeeded];
169 |
170 | NSTimeInterval duration = .666f;
171 | NSTimeInterval delay = 0;
172 | UIViewAnimationOptions options = (UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut);
173 |
174 | CGFloat offset = [self bottomOffset];
175 | [UIView animateWithDuration:duration delay:delay options:options animations:^{
176 | [self.playerView mas_updateConstraints:^(MASConstraintMaker *make) {
177 | make.bottom.equalTo(self.bottomLayout).with.offset(offset);
178 | }];
179 | [self.view layoutIfNeeded];
180 | } completion:^(BOOL finished) {
181 | if (completion)
182 | {
183 | dispatch_async(dispatch_get_main_queue(), ^{
184 | completion();
185 | });
186 | }
187 | }];
188 | }
189 |
190 |
191 | - (void)viewWillLayoutSubviews
192 | {
193 | [self showProgress];
194 | }
195 |
196 |
197 | #pragma mark - Playback controls
198 | - (void)play
199 | {
200 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:willPlayPlayer:), [self.delegate audioViewController:self willPlayPlayer:self.player];)
201 |
202 | [self removeTimeObserver];
203 | [self.player play];
204 | [self addTimeObserver];
205 |
206 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:didPlayPlayer:), [self.delegate audioViewController:self didPlayPlayer:self.player];)
207 | }
208 |
209 |
210 | - (void)pause
211 | {
212 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:willPausePlayer:), [self.delegate audioViewController:self willPausePlayer:self.player];)
213 | [self removeTimeObserver];
214 | [self.player pause];
215 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:didPausePlayer:), [self.delegate audioViewController:self didPausePlayer:self.player];)
216 | }
217 |
218 |
219 | - (void)stop
220 | {
221 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:willStopPlayer:), [self.delegate audioViewController:self willStopPlayer:self.player];)
222 | [[NSNotificationCenter defaultCenter] postNotificationName:LSPAudioPlayerStop object:self];
223 | [self.player stop];
224 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:didStopPlayer:), [self.delegate audioViewController:self didStopPlayer:self.player];)
225 | }
226 |
227 |
228 | - (void)playedUntilEnd:(NSNotification *)notificaiton
229 | {
230 | self.playing = NO;
231 | [self.playerView showLayoutForPlaying:NO];
232 | [self.playerView.progressView setProgress:0];
233 | [self close];
234 | }
235 |
236 |
237 | #pragma mark - Actions
238 |
239 | - (void)playAudioWithURL:(NSURL *)url
240 | {
241 | NSDictionary *userInfo = @{ @"url": url };
242 | [[NSNotificationCenter defaultCenter] postNotificationName:LSPAudioPlayerStart object:self userInfo:userInfo];
243 | [self.player playFromURL:url];
244 | [self playAudioSetupWithURL:url];
245 | }
246 |
247 | - (void)playAudioSetupWithURL:(NSURL *)url
248 | {
249 | [self removeTimeObserver];
250 | [self.model setDestination:url];
251 | [self addTimeObserver];
252 | self.playerView.titleLabel.text = self.model.title;
253 |
254 | self.playing = self.player.isPlaying;
255 |
256 | [self.playerView.progressView setProgress:0];
257 | [self showProgress];
258 |
259 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
260 | [nc removeObserver:self];
261 | [nc addObserver:self selector:@selector(playedUntilEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
262 | }
263 |
264 | - (void)close
265 | {
266 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:willClosePlayer:), [self.delegate audioViewController:self willClosePlayer:self.player];)
267 | [self.player stop];
268 | [self hide:^{
269 | DELEGATE_SAFELY(self.delegate, @selector(audioViewController:didClosePlayer:), [self.delegate audioViewController:self didClosePlayer:self.player];)
270 | [self reset];
271 | }];
272 | }
273 |
274 |
275 | - (void)closeButtonPressed:(UIButton *)button
276 | {
277 | [self close];
278 | }
279 |
280 |
281 | - (void)togglePlayPause:(UIButton *)button
282 | {
283 | if (self.player.isPlaying)
284 | {
285 | [self pause];
286 | }
287 | else
288 | {
289 | [self play];
290 | }
291 |
292 | self.playing = self.player.isPlaying;
293 | }
294 |
295 |
296 | #pragma mark - Progress
297 | - (void)showProgress
298 | {
299 | if (self.player.status != AVPlayerStatusReadyToPlay) return;
300 |
301 | [self updateProgress];
302 | [self updateTimeLabel];
303 | }
304 |
305 |
306 | - (void)updateProgress
307 | {
308 | Float64 duration = CMTimeGetSeconds(self.player.duration);
309 | Float64 current = CMTimeGetSeconds(self.player.currentTime);
310 |
311 | if (!isnormal(duration)) return;
312 |
313 | Float64 progress = (current / duration);
314 | [self.playerView.progressView setProgress:progress];
315 | }
316 |
317 |
318 | - (void)updateTimeLabel
319 | {
320 | NSString *duration = [LSPCMTimeHelper readableCMTime:self.player.duration];
321 | NSString *current = [LSPCMTimeHelper readableCMTime:self.player.currentTime];
322 | [self.playerView setCurrentProgress:current forDuration:duration];
323 | }
324 |
325 |
326 | - (void)jumpToProgress:(Float64)progress
327 | {
328 | [self.player jumpToProgress:progress];
329 | [self showProgress];
330 | }
331 |
332 |
333 | #pragma mark - Helpers
334 | - (void)addTimeObserver
335 | {
336 | __weak __typeof(self)weakself = self;
337 | [self.player addProgressObserver:^(CMTime time) {
338 | [weakself showProgress];
339 | }];
340 | }
341 |
342 |
343 | - (void)removeTimeObserver
344 | {
345 | if (!_progressObserver) return;
346 |
347 | [self.player removeProgressObserver:_progressObserver];
348 | _progressObserver = nil;
349 | }
350 |
351 |
352 | - (BOOL)isPlaying
353 | {
354 | return self.player.isPlaying;
355 | }
356 |
357 |
358 | - (CGFloat)bottomOffset
359 | {
360 | CGFloat safeAreaInset = 0;
361 | if (@available(iOS 11, *))
362 | {
363 | safeAreaInset = self.view.safeAreaInsets.bottom;
364 | }
365 | return safeAreaInset + CGRectGetHeight(self.playerView.frame);
366 | }
367 |
368 |
369 | - (MASViewAttribute *)bottomLayout
370 | {
371 | MASViewAttribute *bottom = self.view.mas_bottom;
372 | if (@available(iOS 11, *))
373 | {
374 | bottom = self.view.mas_safeAreaLayoutGuideBottom;
375 | }
376 |
377 | return bottom;
378 | }
379 |
380 |
381 | #pragma mark - Gestures
382 | - (void)handleTimelineTap:(UITapGestureRecognizer *)gesture
383 | {
384 | BOOL validTap = (gesture.state == UIGestureRecognizerStateEnded && self.playerView.progressView.hidden == NO);
385 | if (validTap)
386 | {
387 | Float64 progress = [self progressFromGesture:gesture];
388 | [self jumpToProgress:progress];
389 | }
390 | }
391 |
392 |
393 | // TODO: Pause while scrubbing, play when gesture is done
394 | - (void)handleTimelinePan:(UIPanGestureRecognizer *)gesture
395 | {
396 | BOOL validPan = (gesture.state == UIGestureRecognizerStateChanged && self.playerView.progressView.hidden == NO);
397 | if (validPan)
398 | {
399 | Float64 progress = [self progressFromGesture:gesture];
400 | [self jumpToProgress:progress];
401 | }
402 | }
403 |
404 |
405 | - (Float64)progressFromGesture:(UIGestureRecognizer *)gesture
406 | {
407 | CGPoint location = [gesture locationOfTouch:0 inView:self.playerView.progressView];
408 | CGFloat xPos = location.x;
409 | return (Float64)xPos/CGRectGetWidth(self.playerView.progressView.frame);
410 | }
411 |
412 |
413 | @end
414 |
--------------------------------------------------------------------------------
/Example/loudspeaker.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4CB019164D0B45776C000F6E /* Pods_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A1340D735A19B0195BF9D95 /* Pods_Tests.framework */; };
11 | 5999A69D261A1BE94A8873A0 /* Pods_loudspeaker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A96B09DA14504E865B964CDD /* Pods_loudspeaker.framework */; };
12 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
13 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
14 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
15 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; };
16 | 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; };
17 | 6003F59E195388D20070C39A /* LSPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* LSPAppDelegate.m */; };
18 | 6003F5A1195388D20070C39A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6003F59F195388D20070C39A /* Main.storyboard */; };
19 | 6003F5A7195388D20070C39A /* LSPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* LSPViewController.m */; };
20 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; };
21 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
22 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
23 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
24 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
25 | 6003F5BC195388D20070C39A /* LSPAudioPlayerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* LSPAudioPlayerSpec.m */; };
26 | 66275C8C19BA808800E74C05 /* Nyan-Cat-Test.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 66275C8B19BA808800E74C05 /* Nyan-Cat-Test.mp3 */; };
27 | 6644D01519B1B06A006521EC /* Nyan-Cat.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6644D01419B1B06A006521EC /* Nyan-Cat.mp3 */; };
28 | 6649A3DF1FBF545F00C4A1DD /* LSPCMTimeHelperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6649A3DE1FBF545F00C4A1DD /* LSPCMTimeHelperSpec.m */; };
29 | 6697C9DB1FC388CF003311FC /* LSPAudioViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6697C9DA1FC388CF003311FC /* LSPAudioViewSpec.m */; };
30 | 6697C9DD1FC38DF1003311FC /* LSPProgressViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6697C9DC1FC38DF1003311FC /* LSPProgressViewSpec.m */; };
31 | 6697C9DF1FC395F4003311FC /* LSPConfigurationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6697C9DE1FC395F4003311FC /* LSPConfigurationSpec.m */; };
32 | 66A27D3D1FBE678B00BAA835 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 66A27D3C1FBE678B00BAA835 /* Launch.storyboard */; };
33 | /* End PBXBuildFile section */
34 |
35 | /* Begin PBXContainerItemProxy section */
36 | 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
37 | isa = PBXContainerItemProxy;
38 | containerPortal = 6003F582195388D10070C39A /* Project object */;
39 | proxyType = 1;
40 | remoteGlobalIDString = 6003F589195388D20070C39A;
41 | remoteInfo = loudspeaker;
42 | };
43 | /* End PBXContainerItemProxy section */
44 |
45 | /* Begin PBXFileReference section */
46 | 04CE74406D184CFBBBA1F083 /* loudspeaker.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = loudspeaker.podspec; path = ../loudspeaker.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
47 | 120B8E2BB30D71BE484C407B /* Pods-loudspeaker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-loudspeaker.release.xcconfig"; path = "Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker.release.xcconfig"; sourceTree = ""; };
48 | 277C080A6A344991E9CCDE66 /* Pods-loudspeaker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-loudspeaker.debug.xcconfig"; path = "Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker.debug.xcconfig"; sourceTree = ""; };
49 | 5A1340D735A19B0195BF9D95 /* Pods_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 6003F58A195388D20070C39A /* loudspeaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = loudspeaker.app; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
52 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
53 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
54 | 6003F595195388D20070C39A /* loudspeaker-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "loudspeaker-Info.plist"; sourceTree = ""; };
55 | 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
56 | 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
57 | 6003F59B195388D20070C39A /* loudspeaker-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "loudspeaker-Prefix.pch"; sourceTree = ""; };
58 | 6003F59C195388D20070C39A /* LSPAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LSPAppDelegate.h; sourceTree = ""; };
59 | 6003F59D195388D20070C39A /* LSPAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPAppDelegate.m; sourceTree = ""; };
60 | 6003F5A0195388D20070C39A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
61 | 6003F5A5195388D20070C39A /* LSPViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LSPViewController.h; sourceTree = ""; };
62 | 6003F5A6195388D20070C39A /* LSPViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPViewController.m; sourceTree = ""; };
63 | 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
64 | 6003F5AE195388D20070C39A /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
66 | 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; };
67 | 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
68 | 6003F5BB195388D20070C39A /* LSPAudioPlayerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPAudioPlayerSpec.m; sourceTree = ""; };
69 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; };
70 | 66275C8B19BA808800E74C05 /* Nyan-Cat-Test.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Nyan-Cat-Test.mp3"; sourceTree = ""; };
71 | 6644D01419B1B06A006521EC /* Nyan-Cat.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Nyan-Cat.mp3"; sourceTree = ""; };
72 | 6649A3DE1FBF545F00C4A1DD /* LSPCMTimeHelperSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPCMTimeHelperSpec.m; sourceTree = ""; };
73 | 6697C9DA1FC388CF003311FC /* LSPAudioViewSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPAudioViewSpec.m; sourceTree = ""; };
74 | 6697C9DC1FC38DF1003311FC /* LSPProgressViewSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPProgressViewSpec.m; sourceTree = ""; };
75 | 6697C9DE1FC395F4003311FC /* LSPConfigurationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LSPConfigurationSpec.m; sourceTree = ""; };
76 | 66A27D3C1FBE678B00BAA835 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = ""; };
77 | A96B09DA14504E865B964CDD /* Pods_loudspeaker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_loudspeaker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78 | AF755EAFAF0B404E8A8F022C /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
79 | BBDDD2935A556DB5BD8B2D97 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = ""; };
80 | CC4EB31AA02B580F6A5F7C00 /* Pods-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.release.xcconfig"; sourceTree = ""; };
81 | DE8FBD6FCCE74BAE960063B5 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
82 | /* End PBXFileReference section */
83 |
84 | /* Begin PBXFrameworksBuildPhase section */
85 | 6003F587195388D20070C39A /* Frameworks */ = {
86 | isa = PBXFrameworksBuildPhase;
87 | buildActionMask = 2147483647;
88 | files = (
89 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
90 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
91 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
92 | 5999A69D261A1BE94A8873A0 /* Pods_loudspeaker.framework in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | 6003F5AB195388D20070C39A /* Frameworks */ = {
97 | isa = PBXFrameworksBuildPhase;
98 | buildActionMask = 2147483647;
99 | files = (
100 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
101 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
102 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
103 | 4CB019164D0B45776C000F6E /* Pods_Tests.framework in Frameworks */,
104 | );
105 | runOnlyForDeploymentPostprocessing = 0;
106 | };
107 | /* End PBXFrameworksBuildPhase section */
108 |
109 | /* Begin PBXGroup section */
110 | 6003F581195388D10070C39A = {
111 | isa = PBXGroup;
112 | children = (
113 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
114 | 6003F593195388D20070C39A /* loudspeaker */,
115 | 6003F5B5195388D20070C39A /* Tests */,
116 | 6003F58C195388D20070C39A /* Frameworks */,
117 | 6003F58B195388D20070C39A /* Products */,
118 | A4137025745AAC23586B180F /* Pods */,
119 | );
120 | sourceTree = "";
121 | };
122 | 6003F58B195388D20070C39A /* Products */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 6003F58A195388D20070C39A /* loudspeaker.app */,
126 | 6003F5AE195388D20070C39A /* Tests.xctest */,
127 | );
128 | name = Products;
129 | sourceTree = "";
130 | };
131 | 6003F58C195388D20070C39A /* Frameworks */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 6003F58D195388D20070C39A /* Foundation.framework */,
135 | 6003F58F195388D20070C39A /* CoreGraphics.framework */,
136 | 6003F591195388D20070C39A /* UIKit.framework */,
137 | 6003F5AF195388D20070C39A /* XCTest.framework */,
138 | 5A1340D735A19B0195BF9D95 /* Pods_Tests.framework */,
139 | A96B09DA14504E865B964CDD /* Pods_loudspeaker.framework */,
140 | );
141 | name = Frameworks;
142 | sourceTree = "";
143 | };
144 | 6003F593195388D20070C39A /* loudspeaker */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 6003F5A8195388D20070C39A /* Images.xcassets */,
148 | 66A27D3C1FBE678B00BAA835 /* Launch.storyboard */,
149 | 6003F59C195388D20070C39A /* LSPAppDelegate.h */,
150 | 6003F59D195388D20070C39A /* LSPAppDelegate.m */,
151 | 6003F5A5195388D20070C39A /* LSPViewController.h */,
152 | 6003F5A6195388D20070C39A /* LSPViewController.m */,
153 | 6003F59F195388D20070C39A /* Main.storyboard */,
154 | 6003F594195388D20070C39A /* Supporting Files */,
155 | );
156 | path = loudspeaker;
157 | sourceTree = "";
158 | };
159 | 6003F594195388D20070C39A /* Supporting Files */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 6644D01419B1B06A006521EC /* Nyan-Cat.mp3 */,
163 | 6003F595195388D20070C39A /* loudspeaker-Info.plist */,
164 | 6003F596195388D20070C39A /* InfoPlist.strings */,
165 | 6003F599195388D20070C39A /* main.m */,
166 | 6003F59B195388D20070C39A /* loudspeaker-Prefix.pch */,
167 | );
168 | name = "Supporting Files";
169 | sourceTree = "";
170 | };
171 | 6003F5B5195388D20070C39A /* Tests */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 6003F5BB195388D20070C39A /* LSPAudioPlayerSpec.m */,
175 | 6697C9DA1FC388CF003311FC /* LSPAudioViewSpec.m */,
176 | 6649A3DE1FBF545F00C4A1DD /* LSPCMTimeHelperSpec.m */,
177 | 6697C9DE1FC395F4003311FC /* LSPConfigurationSpec.m */,
178 | 6697C9DC1FC38DF1003311FC /* LSPProgressViewSpec.m */,
179 | 6003F5B6195388D20070C39A /* Supporting Files */,
180 | );
181 | path = Tests;
182 | sourceTree = "";
183 | };
184 | 6003F5B6195388D20070C39A /* Supporting Files */ = {
185 | isa = PBXGroup;
186 | children = (
187 | 66275C8B19BA808800E74C05 /* Nyan-Cat-Test.mp3 */,
188 | 6003F5B7195388D20070C39A /* Tests-Info.plist */,
189 | 6003F5B8195388D20070C39A /* InfoPlist.strings */,
190 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */,
191 | );
192 | name = "Supporting Files";
193 | sourceTree = "";
194 | };
195 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = {
196 | isa = PBXGroup;
197 | children = (
198 | 04CE74406D184CFBBBA1F083 /* loudspeaker.podspec */,
199 | DE8FBD6FCCE74BAE960063B5 /* README.md */,
200 | AF755EAFAF0B404E8A8F022C /* LICENSE */,
201 | );
202 | name = "Podspec Metadata";
203 | sourceTree = "";
204 | };
205 | A4137025745AAC23586B180F /* Pods */ = {
206 | isa = PBXGroup;
207 | children = (
208 | BBDDD2935A556DB5BD8B2D97 /* Pods-Tests.debug.xcconfig */,
209 | CC4EB31AA02B580F6A5F7C00 /* Pods-Tests.release.xcconfig */,
210 | 277C080A6A344991E9CCDE66 /* Pods-loudspeaker.debug.xcconfig */,
211 | 120B8E2BB30D71BE484C407B /* Pods-loudspeaker.release.xcconfig */,
212 | );
213 | name = Pods;
214 | sourceTree = "";
215 | };
216 | /* End PBXGroup section */
217 |
218 | /* Begin PBXNativeTarget section */
219 | 6003F589195388D20070C39A /* loudspeaker */ = {
220 | isa = PBXNativeTarget;
221 | buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "loudspeaker" */;
222 | buildPhases = (
223 | 6A78E2D99612904378871230 /* [CP] Check Pods Manifest.lock */,
224 | 6003F586195388D20070C39A /* Sources */,
225 | 6003F587195388D20070C39A /* Frameworks */,
226 | 6003F588195388D20070C39A /* Resources */,
227 | D29F17EAAAA416CD1A65A5D5 /* [CP] Embed Pods Frameworks */,
228 | 5FB7ABD54F6F6AB3BC115B1A /* [CP] Copy Pods Resources */,
229 | );
230 | buildRules = (
231 | );
232 | dependencies = (
233 | );
234 | name = loudspeaker;
235 | productName = loudspeaker;
236 | productReference = 6003F58A195388D20070C39A /* loudspeaker.app */;
237 | productType = "com.apple.product-type.application";
238 | };
239 | 6003F5AD195388D20070C39A /* Tests */ = {
240 | isa = PBXNativeTarget;
241 | buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tests" */;
242 | buildPhases = (
243 | C2D6CA83C0B94A0BD33CD7DC /* [CP] Check Pods Manifest.lock */,
244 | 6003F5AA195388D20070C39A /* Sources */,
245 | 6003F5AB195388D20070C39A /* Frameworks */,
246 | 6003F5AC195388D20070C39A /* Resources */,
247 | 700189DE91770B50B11F4E3B /* [CP] Embed Pods Frameworks */,
248 | 5D3ACD3DAD153EA9B7AE1489 /* [CP] Copy Pods Resources */,
249 | );
250 | buildRules = (
251 | );
252 | dependencies = (
253 | 6003F5B4195388D20070C39A /* PBXTargetDependency */,
254 | );
255 | name = Tests;
256 | productName = loudspeakerTests;
257 | productReference = 6003F5AE195388D20070C39A /* Tests.xctest */;
258 | productType = "com.apple.product-type.bundle.unit-test";
259 | };
260 | /* End PBXNativeTarget section */
261 |
262 | /* Begin PBXProject section */
263 | 6003F582195388D10070C39A /* Project object */ = {
264 | isa = PBXProject;
265 | attributes = {
266 | CLASSPREFIX = LSP;
267 | LastUpgradeCheck = 0900;
268 | ORGANIZATIONNAME = "Adam Yanalunas";
269 | TargetAttributes = {
270 | 6003F589195388D20070C39A = {
271 | ProvisioningStyle = Automatic;
272 | };
273 | 6003F5AD195388D20070C39A = {
274 | TestTargetID = 6003F589195388D20070C39A;
275 | };
276 | };
277 | };
278 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "loudspeaker" */;
279 | compatibilityVersion = "Xcode 3.2";
280 | developmentRegion = English;
281 | hasScannedForEncodings = 0;
282 | knownRegions = (
283 | en,
284 | Base,
285 | );
286 | mainGroup = 6003F581195388D10070C39A;
287 | productRefGroup = 6003F58B195388D20070C39A /* Products */;
288 | projectDirPath = "";
289 | projectRoot = "";
290 | targets = (
291 | 6003F589195388D20070C39A /* loudspeaker */,
292 | 6003F5AD195388D20070C39A /* Tests */,
293 | );
294 | };
295 | /* End PBXProject section */
296 |
297 | /* Begin PBXResourcesBuildPhase section */
298 | 6003F588195388D20070C39A /* Resources */ = {
299 | isa = PBXResourcesBuildPhase;
300 | buildActionMask = 2147483647;
301 | files = (
302 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
303 | 66A27D3D1FBE678B00BAA835 /* Launch.storyboard in Resources */,
304 | 6003F5A1195388D20070C39A /* Main.storyboard in Resources */,
305 | 6644D01519B1B06A006521EC /* Nyan-Cat.mp3 in Resources */,
306 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
307 | );
308 | runOnlyForDeploymentPostprocessing = 0;
309 | };
310 | 6003F5AC195388D20070C39A /* Resources */ = {
311 | isa = PBXResourcesBuildPhase;
312 | buildActionMask = 2147483647;
313 | files = (
314 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
315 | 66275C8C19BA808800E74C05 /* Nyan-Cat-Test.mp3 in Resources */,
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | };
319 | /* End PBXResourcesBuildPhase section */
320 |
321 | /* Begin PBXShellScriptBuildPhase section */
322 | 5D3ACD3DAD153EA9B7AE1489 /* [CP] Copy Pods Resources */ = {
323 | isa = PBXShellScriptBuildPhase;
324 | buildActionMask = 2147483647;
325 | files = (
326 | );
327 | inputPaths = (
328 | );
329 | name = "[CP] Copy Pods Resources";
330 | outputPaths = (
331 | );
332 | runOnlyForDeploymentPostprocessing = 0;
333 | shellPath = /bin/sh;
334 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests/Pods-Tests-resources.sh\"\n";
335 | showEnvVarsInLog = 0;
336 | };
337 | 5FB7ABD54F6F6AB3BC115B1A /* [CP] Copy Pods Resources */ = {
338 | isa = PBXShellScriptBuildPhase;
339 | buildActionMask = 2147483647;
340 | files = (
341 | );
342 | inputPaths = (
343 | "${SRCROOT}/Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker-resources.sh",
344 | $PODS_CONFIGURATION_BUILD_DIR/loudspeaker/loudspeaker.bundle,
345 | );
346 | name = "[CP] Copy Pods Resources";
347 | outputPaths = (
348 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
349 | );
350 | runOnlyForDeploymentPostprocessing = 0;
351 | shellPath = /bin/sh;
352 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker-resources.sh\"\n";
353 | showEnvVarsInLog = 0;
354 | };
355 | 6A78E2D99612904378871230 /* [CP] Check Pods Manifest.lock */ = {
356 | isa = PBXShellScriptBuildPhase;
357 | buildActionMask = 2147483647;
358 | files = (
359 | );
360 | inputPaths = (
361 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
362 | "${PODS_ROOT}/Manifest.lock",
363 | );
364 | name = "[CP] Check Pods Manifest.lock";
365 | outputPaths = (
366 | "$(DERIVED_FILE_DIR)/Pods-loudspeaker-checkManifestLockResult.txt",
367 | );
368 | runOnlyForDeploymentPostprocessing = 0;
369 | shellPath = /bin/sh;
370 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
371 | showEnvVarsInLog = 0;
372 | };
373 | 700189DE91770B50B11F4E3B /* [CP] Embed Pods Frameworks */ = {
374 | isa = PBXShellScriptBuildPhase;
375 | buildActionMask = 2147483647;
376 | files = (
377 | );
378 | inputPaths = (
379 | "${SRCROOT}/Pods/Target Support Files/Pods-Tests/Pods-Tests-frameworks.sh",
380 | "${BUILT_PRODUCTS_DIR}/Expecta/Expecta.framework",
381 | "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
382 | "${BUILT_PRODUCTS_DIR}/Specta/Specta.framework",
383 | );
384 | name = "[CP] Embed Pods Frameworks";
385 | outputPaths = (
386 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Expecta.framework",
387 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
388 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Specta.framework",
389 | );
390 | runOnlyForDeploymentPostprocessing = 0;
391 | shellPath = /bin/sh;
392 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests/Pods-Tests-frameworks.sh\"\n";
393 | showEnvVarsInLog = 0;
394 | };
395 | C2D6CA83C0B94A0BD33CD7DC /* [CP] Check Pods Manifest.lock */ = {
396 | isa = PBXShellScriptBuildPhase;
397 | buildActionMask = 2147483647;
398 | files = (
399 | );
400 | inputPaths = (
401 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
402 | "${PODS_ROOT}/Manifest.lock",
403 | );
404 | name = "[CP] Check Pods Manifest.lock";
405 | outputPaths = (
406 | "$(DERIVED_FILE_DIR)/Pods-Tests-checkManifestLockResult.txt",
407 | );
408 | runOnlyForDeploymentPostprocessing = 0;
409 | shellPath = /bin/sh;
410 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
411 | showEnvVarsInLog = 0;
412 | };
413 | D29F17EAAAA416CD1A65A5D5 /* [CP] Embed Pods Frameworks */ = {
414 | isa = PBXShellScriptBuildPhase;
415 | buildActionMask = 2147483647;
416 | files = (
417 | );
418 | inputPaths = (
419 | "${SRCROOT}/Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker-frameworks.sh",
420 | "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework",
421 | "${BUILT_PRODUCTS_DIR}/loudspeaker/loudspeaker.framework",
422 | );
423 | name = "[CP] Embed Pods Frameworks";
424 | outputPaths = (
425 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework",
426 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/loudspeaker.framework",
427 | );
428 | runOnlyForDeploymentPostprocessing = 0;
429 | shellPath = /bin/sh;
430 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-loudspeaker/Pods-loudspeaker-frameworks.sh\"\n";
431 | showEnvVarsInLog = 0;
432 | };
433 | /* End PBXShellScriptBuildPhase section */
434 |
435 | /* Begin PBXSourcesBuildPhase section */
436 | 6003F586195388D20070C39A /* Sources */ = {
437 | isa = PBXSourcesBuildPhase;
438 | buildActionMask = 2147483647;
439 | files = (
440 | 6003F59E195388D20070C39A /* LSPAppDelegate.m in Sources */,
441 | 6003F5A7195388D20070C39A /* LSPViewController.m in Sources */,
442 | 6003F59A195388D20070C39A /* main.m in Sources */,
443 | );
444 | runOnlyForDeploymentPostprocessing = 0;
445 | };
446 | 6003F5AA195388D20070C39A /* Sources */ = {
447 | isa = PBXSourcesBuildPhase;
448 | buildActionMask = 2147483647;
449 | files = (
450 | 6003F5BC195388D20070C39A /* LSPAudioPlayerSpec.m in Sources */,
451 | 6697C9DF1FC395F4003311FC /* LSPConfigurationSpec.m in Sources */,
452 | 6697C9DB1FC388CF003311FC /* LSPAudioViewSpec.m in Sources */,
453 | 6697C9DD1FC38DF1003311FC /* LSPProgressViewSpec.m in Sources */,
454 | 6649A3DF1FBF545F00C4A1DD /* LSPCMTimeHelperSpec.m in Sources */,
455 | );
456 | runOnlyForDeploymentPostprocessing = 0;
457 | };
458 | /* End PBXSourcesBuildPhase section */
459 |
460 | /* Begin PBXTargetDependency section */
461 | 6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
462 | isa = PBXTargetDependency;
463 | target = 6003F589195388D20070C39A /* loudspeaker */;
464 | targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
465 | };
466 | /* End PBXTargetDependency section */
467 |
468 | /* Begin PBXVariantGroup section */
469 | 6003F596195388D20070C39A /* InfoPlist.strings */ = {
470 | isa = PBXVariantGroup;
471 | children = (
472 | 6003F597195388D20070C39A /* en */,
473 | );
474 | name = InfoPlist.strings;
475 | sourceTree = "";
476 | };
477 | 6003F59F195388D20070C39A /* Main.storyboard */ = {
478 | isa = PBXVariantGroup;
479 | children = (
480 | 6003F5A0195388D20070C39A /* Base */,
481 | );
482 | name = Main.storyboard;
483 | sourceTree = "";
484 | };
485 | 6003F5B8195388D20070C39A /* InfoPlist.strings */ = {
486 | isa = PBXVariantGroup;
487 | children = (
488 | 6003F5B9195388D20070C39A /* en */,
489 | );
490 | name = InfoPlist.strings;
491 | sourceTree = "";
492 | };
493 | /* End PBXVariantGroup section */
494 |
495 | /* Begin XCBuildConfiguration section */
496 | 6003F5BD195388D20070C39A /* Debug */ = {
497 | isa = XCBuildConfiguration;
498 | buildSettings = {
499 | ALWAYS_SEARCH_USER_PATHS = NO;
500 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
501 | CLANG_CXX_LIBRARY = "libc++";
502 | CLANG_ENABLE_MODULES = YES;
503 | CLANG_ENABLE_OBJC_ARC = YES;
504 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
505 | CLANG_WARN_BOOL_CONVERSION = YES;
506 | CLANG_WARN_COMMA = YES;
507 | CLANG_WARN_CONSTANT_CONVERSION = YES;
508 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
509 | CLANG_WARN_EMPTY_BODY = YES;
510 | CLANG_WARN_ENUM_CONVERSION = YES;
511 | CLANG_WARN_INFINITE_RECURSION = YES;
512 | CLANG_WARN_INT_CONVERSION = YES;
513 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
514 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
515 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
516 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
517 | CLANG_WARN_STRICT_PROTOTYPES = YES;
518 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
519 | CLANG_WARN_UNREACHABLE_CODE = YES;
520 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
521 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
522 | COPY_PHASE_STRIP = NO;
523 | ENABLE_STRICT_OBJC_MSGSEND = YES;
524 | ENABLE_TESTABILITY = YES;
525 | GCC_C_LANGUAGE_STANDARD = gnu99;
526 | GCC_DYNAMIC_NO_PIC = NO;
527 | GCC_NO_COMMON_BLOCKS = YES;
528 | GCC_OPTIMIZATION_LEVEL = 0;
529 | GCC_PREPROCESSOR_DEFINITIONS = (
530 | "DEBUG=1",
531 | "$(inherited)",
532 | );
533 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
534 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
535 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
536 | GCC_WARN_UNDECLARED_SELECTOR = YES;
537 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
538 | GCC_WARN_UNUSED_FUNCTION = YES;
539 | GCC_WARN_UNUSED_VARIABLE = YES;
540 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
541 | ONLY_ACTIVE_ARCH = YES;
542 | SDKROOT = iphoneos;
543 | TARGETED_DEVICE_FAMILY = "1,2";
544 | };
545 | name = Debug;
546 | };
547 | 6003F5BE195388D20070C39A /* Release */ = {
548 | isa = XCBuildConfiguration;
549 | buildSettings = {
550 | ALWAYS_SEARCH_USER_PATHS = NO;
551 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
552 | CLANG_CXX_LIBRARY = "libc++";
553 | CLANG_ENABLE_MODULES = YES;
554 | CLANG_ENABLE_OBJC_ARC = YES;
555 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
556 | CLANG_WARN_BOOL_CONVERSION = YES;
557 | CLANG_WARN_COMMA = YES;
558 | CLANG_WARN_CONSTANT_CONVERSION = YES;
559 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
560 | CLANG_WARN_EMPTY_BODY = YES;
561 | CLANG_WARN_ENUM_CONVERSION = YES;
562 | CLANG_WARN_INFINITE_RECURSION = YES;
563 | CLANG_WARN_INT_CONVERSION = YES;
564 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
565 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
566 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
567 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
568 | CLANG_WARN_STRICT_PROTOTYPES = YES;
569 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
570 | CLANG_WARN_UNREACHABLE_CODE = YES;
571 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
572 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
573 | COPY_PHASE_STRIP = YES;
574 | ENABLE_NS_ASSERTIONS = NO;
575 | ENABLE_STRICT_OBJC_MSGSEND = YES;
576 | GCC_C_LANGUAGE_STANDARD = gnu99;
577 | GCC_NO_COMMON_BLOCKS = YES;
578 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
579 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
580 | GCC_WARN_UNDECLARED_SELECTOR = YES;
581 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
582 | GCC_WARN_UNUSED_FUNCTION = YES;
583 | GCC_WARN_UNUSED_VARIABLE = YES;
584 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
585 | SDKROOT = iphoneos;
586 | TARGETED_DEVICE_FAMILY = "1,2";
587 | VALIDATE_PRODUCT = YES;
588 | };
589 | name = Release;
590 | };
591 | 6003F5C0195388D20070C39A /* Debug */ = {
592 | isa = XCBuildConfiguration;
593 | baseConfigurationReference = 277C080A6A344991E9CCDE66 /* Pods-loudspeaker.debug.xcconfig */;
594 | buildSettings = {
595 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
596 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
597 | CODE_SIGN_STYLE = Automatic;
598 | DEVELOPMENT_TEAM = "";
599 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
600 | GCC_PREFIX_HEADER = "loudspeaker/loudspeaker-Prefix.pch";
601 | INFOPLIST_FILE = "loudspeaker/loudspeaker-Info.plist";
602 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
603 | PRODUCT_NAME = "$(TARGET_NAME)";
604 | PROVISIONING_PROFILE = "";
605 | PROVISIONING_PROFILE_SPECIFIER = "";
606 | WRAPPER_EXTENSION = app;
607 | };
608 | name = Debug;
609 | };
610 | 6003F5C1195388D20070C39A /* Release */ = {
611 | isa = XCBuildConfiguration;
612 | baseConfigurationReference = 120B8E2BB30D71BE484C407B /* Pods-loudspeaker.release.xcconfig */;
613 | buildSettings = {
614 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
615 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
616 | CODE_SIGN_STYLE = Automatic;
617 | DEVELOPMENT_TEAM = "";
618 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
619 | GCC_PREFIX_HEADER = "loudspeaker/loudspeaker-Prefix.pch";
620 | INFOPLIST_FILE = "loudspeaker/loudspeaker-Info.plist";
621 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
622 | PRODUCT_NAME = "$(TARGET_NAME)";
623 | PROVISIONING_PROFILE_SPECIFIER = "";
624 | WRAPPER_EXTENSION = app;
625 | };
626 | name = Release;
627 | };
628 | 6003F5C3195388D20070C39A /* Debug */ = {
629 | isa = XCBuildConfiguration;
630 | baseConfigurationReference = BBDDD2935A556DB5BD8B2D97 /* Pods-Tests.debug.xcconfig */;
631 | buildSettings = {
632 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/loudspeaker.app/loudspeaker";
633 | FRAMEWORK_SEARCH_PATHS = (
634 | "$(inherited)",
635 | "$(DEVELOPER_FRAMEWORKS_DIR)",
636 | );
637 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
638 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
639 | GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
640 | INFOPLIST_FILE = "Tests/Tests-Info.plist";
641 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
642 | PRODUCT_NAME = "$(TARGET_NAME)";
643 | TEST_HOST = "$(BUNDLE_LOADER)";
644 | WRAPPER_EXTENSION = xctest;
645 | };
646 | name = Debug;
647 | };
648 | 6003F5C4195388D20070C39A /* Release */ = {
649 | isa = XCBuildConfiguration;
650 | baseConfigurationReference = CC4EB31AA02B580F6A5F7C00 /* Pods-Tests.release.xcconfig */;
651 | buildSettings = {
652 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/loudspeaker.app/loudspeaker";
653 | FRAMEWORK_SEARCH_PATHS = (
654 | "$(inherited)",
655 | "$(DEVELOPER_FRAMEWORKS_DIR)",
656 | );
657 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
658 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
659 | INFOPLIST_FILE = "Tests/Tests-Info.plist";
660 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
661 | PRODUCT_NAME = "$(TARGET_NAME)";
662 | TEST_HOST = "$(BUNDLE_LOADER)";
663 | WRAPPER_EXTENSION = xctest;
664 | };
665 | name = Release;
666 | };
667 | /* End XCBuildConfiguration section */
668 |
669 | /* Begin XCConfigurationList section */
670 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "loudspeaker" */ = {
671 | isa = XCConfigurationList;
672 | buildConfigurations = (
673 | 6003F5BD195388D20070C39A /* Debug */,
674 | 6003F5BE195388D20070C39A /* Release */,
675 | );
676 | defaultConfigurationIsVisible = 0;
677 | defaultConfigurationName = Release;
678 | };
679 | 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "loudspeaker" */ = {
680 | isa = XCConfigurationList;
681 | buildConfigurations = (
682 | 6003F5C0195388D20070C39A /* Debug */,
683 | 6003F5C1195388D20070C39A /* Release */,
684 | );
685 | defaultConfigurationIsVisible = 0;
686 | defaultConfigurationName = Release;
687 | };
688 | 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tests" */ = {
689 | isa = XCConfigurationList;
690 | buildConfigurations = (
691 | 6003F5C3195388D20070C39A /* Debug */,
692 | 6003F5C4195388D20070C39A /* Release */,
693 | );
694 | defaultConfigurationIsVisible = 0;
695 | defaultConfigurationName = Release;
696 | };
697 | /* End XCConfigurationList section */
698 | };
699 | rootObject = 6003F582195388D10070C39A /* Project object */;
700 | }
701 |
--------------------------------------------------------------------------------