Commit 698b0359 cgx

引入DOUAudioStreamer

1 个父辈 bc6a86ac
正在显示 71 个修改的文件 包含 6117 行增加68 行删除
......@@ -58,6 +58,7 @@
D0CFD3D027FB3B920002982B /* launcher@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0CFD3CD27FB3B910002982B /* launcher@3x.png */; };
D0CFD3D127FB3B920002982B /* launcher.png in Resources */ = {isa = PBXBuildFile; fileRef = D0CFD3CE27FB3B920002982B /* launcher.png */; };
D0E9408927FE96A900D57495 /* libWeChatSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E9408727FE96A900D57495 /* libWeChatSDK.a */; settings = {ATTRIBUTES = (Required, ); }; };
D0F808F52803D4E70097899F /* Track.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F808F32803D4E70097899F /* Track.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -161,6 +162,8 @@
D0E9408627FE96A900D57495 /* WechatAuthSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WechatAuthSDK.h; sourceTree = "<group>"; };
D0E9408727FE96A900D57495 /* libWeChatSDK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libWeChatSDK.a; sourceTree = "<group>"; };
D0F808ED2803C83A0097899F /* DreamSleepDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DreamSleepDebug.entitlements; sourceTree = "<group>"; };
D0F808F32803D4E70097899F /* Track.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Track.m; sourceTree = "<group>"; };
D0F808F42803D4E70097899F /* Track.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Track.h; sourceTree = "<group>"; };
F02C34A5649294F60932630C /* Pods-DreamSleep.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DreamSleep.release.xcconfig"; path = "Target Support Files/Pods-DreamSleep/Pods-DreamSleep.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
......@@ -297,6 +300,8 @@
D01814DE27FFDB6A00583D4E /* HomeTableViewCell.m */,
D0C09EDE28007E5F00709D4C /* BreatheController.h */,
D0C09EDF28007E5F00709D4C /* BreatheController.m */,
D0F808F42803D4E70097899F /* Track.h */,
D0F808F32803D4E70097899F /* Track.m */,
);
path = Home;
sourceTree = "<group>";
......@@ -598,6 +603,7 @@
D01814DC27FFD92200583D4E /* DSDataSource.m in Sources */,
D0B5ECC827F2E97A003EDFE3 /* MacroFuncUtil.m in Sources */,
D01814E227FFDBB800583D4E /* HomeHeaderView.m in Sources */,
D0F808F52803D4E70097899F /* Track.m in Sources */,
D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */,
D0930F122801124E006B497A /* BaseNaviController.m in Sources */,
D0930F1A2801874B006B497A /* UIViewController+Swizzling.m in Sources */,
......@@ -760,7 +766,7 @@
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(inherited)/**";
INFOPLIST_FILE = DreamSleep/DSConfig/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "${Bundle_Display_Name}";
INFOPLIST_KEY_CFBundleDisplayName = "小梦睡眠-Dev";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
......@@ -808,7 +814,7 @@
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(inherited)/**";
INFOPLIST_FILE = DreamSleep/DSConfig/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "${Bundle_Display_Name}";
INFOPLIST_KEY_CFBundleDisplayName = "小梦睡眠-Dev";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
......@@ -914,7 +920,7 @@
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(inherited)/**";
INFOPLIST_FILE = DreamSleep/DSConfig/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "${Bundle_Display_Name}";
INFOPLIST_KEY_CFBundleDisplayName = "小梦睡眠-Dev";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
......
......@@ -27,5 +27,9 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>
......@@ -18,7 +18,10 @@ typedef NS_ENUM(NSInteger, LottieStyle) {
///  均衡呼吸法和舒睡4-7-8呼吸法
@interface BreatheController : UIViewController
// 动画样式
@property (nonatomic, assign) LottieStyle style;
// 音频URL
@property (nonatomic, strong) NSURL *audioFileURL;
@end
NS_ASSUME_NONNULL_END
......@@ -6,20 +6,29 @@
//
#import "BreatheController.h"
#import "Track.h"
#import <Lottie.h>
#import <DOUAudioStreamer.h>
#import <AudioToolbox/AudioToolbox.h>
@interface BreatheController () <UIPickerViewDataSource, UIPickerViewDelegate>
@property (nonatomic, strong) LOTAnimationView *zoomView;
// 旋转lottie
@property (nonatomic, strong) LOTAnimationView *rotateView;
@property (nonatomic, strong) UIPickerView *pickerView;
@property (nonatomic, strong) UILabel *timeLb;
@property (nonatomic, strong) UILabel *minuteLb;
@property (nonatomic, strong) UIButton *starBtn;
@property (nonatomic, strong) NSArray *dataArr;
@property (nonatomic, assign) NSInteger timeAccount;
// 缩放lottie
@property (nonatomic, strong) LOTAnimationView *zoomView;
@property (nonatomic, copy) NSString *rotateLottieFile;
@property (nonatomic, copy) NSString *zoomLottieFile;
@property (nonatomic, strong) UIButton *navRightBtn;
// 分钟选择控件
@property (nonatomic, strong) UIPickerView *minutePickerView;
@property (nonatomic, strong) NSArray *minuteDatas;
@property (nonatomic, strong) UILabel *timeLb;
@property (nonatomic, strong) UILabel *minuteLb;
@property (nonatomic, strong) UIButton *startRelaxBtn;
@property (nonatomic, strong) UIButton *volumeBtn;
@property (nonatomic, strong) DOUAudioStreamer *audioStreamer;
@property (nonatomic, strong) NSTimer *controlTimer;
@property (nonatomic, assign) NSInteger minuteTime;
@property (nonatomic,assign) NSInteger playDuration;
@end
@implementation BreatheController
......@@ -28,12 +37,21 @@
[super viewDidLoad];
self.view.backgroundColor = DarkColor;
self.dataArr = @[@"1",@"2",@"3",@"4",@"5"];
self.minuteTime = 1;
self.minuteDatas = @[@"1",@"2",@"3",@"4",@"5"];
self.rotateLottieFile = self.style == LottieStyleBalance ? @"relax_normal_lottie.json" : @"478normal_lottie.json";
self.zoomLottieFile = self.style == LottieStyleBalance ? @"relax_lottie.json" : @"478_lottie.json";
self.audioFileURL = [NSURL URLWithString:@"https://img2.ydniu.com/audio/1641783843905_LhE13l.mp3"];
[self initView];
[self setNaviRightBtn];
}
- (void)dealloc {
[self cancelAudioStreamer];
[[UIApplication sharedApplication] removeObserver:self forKeyPath:@"idleTimerDisabled"];
[UIApplication sharedApplication].idleTimerDisabled = NO;
}
#pragma mark - 导航栏夜间模式
......@@ -46,10 +64,26 @@
return NO;
}
- (void)keepIdleTimerDisabled {
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
[[UIApplication sharedApplication] addObserver:self forKeyPath:@"idleTimerDisabled" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)cancelAudioStreamer {
[self.controlTimer invalidate];
self.controlTimer = nil;
if (self.audioStreamer) {
[self.audioStreamer stop];
[self.audioStreamer removeObserver:self forKeyPath:@"status" context:kStatusKVOKey1];
self.audioStreamer = nil;
}
}
- (void)initView {
[self.view addSubview:self.rotateView];
[self.view addSubview:self.zoomView];
[self.view addSubview:self.pickerView];
[self.view addSubview:self.minutePickerView];
self.timeLb = [UILabel new];
self.timeLb.text = @"放松时长";
......@@ -65,7 +99,7 @@
self.minuteLb.textColor = [UIColor whiteColor];
[self.minuteLb sizeToFit];
[self.view addSubview:self.starBtn];
[self.view addSubview:self.startRelaxBtn];
[self.rotateView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.equalTo(@162);
......@@ -78,7 +112,7 @@
make.rightMargin.equalTo(self.view).offset(-90);
make.height.equalTo(@300);
}];
[self.pickerView mas_makeConstraints:^(MASConstraintMaker *make) {
[self.minutePickerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.rotateView.mas_bottom).offset(30);
make.left.equalTo(self.timeLb.mas_right);
make.right.equalTo(self.minuteLb.mas_left);
......@@ -86,45 +120,105 @@
}];
[self.timeLb mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.rotateView);
make.centerY.equalTo(self.pickerView.mas_centerY);
make.centerY.equalTo(self.minutePickerView.mas_centerY);
}];
[self.minuteLb mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.rotateView);
make.centerY.equalTo(self.pickerView.mas_centerY);
make.centerY.equalTo(self.minutePickerView.mas_centerY);
}];
[self.starBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.pickerView.mas_bottom).offset(50);
[self.startRelaxBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.minutePickerView.mas_bottom).offset(50);
make.width.equalTo(@160);
make.centerX.equalTo(self.zoomView.mas_centerX);
make.height.equalTo(@40);
}];
// 设置关闭/开启声音按钮
self.volumeBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[self.volumeBtn setImage:[UIImage imageNamed:@"openMusic"] forState:UIControlStateNormal];
[self.volumeBtn setImage:[UIImage imageNamed:@"closeMusic"] forState:UIControlStateSelected];
[self.volumeBtn addTarget:self action:@selector(volumeControlAction:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.volumeBtn];
// 设置屏幕常亮
[self keepIdleTimerDisabled];
}
- (void)setNaviRightBtn {
self.navRightBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[self.navRightBtn setImage:[UIImage imageNamed:@"openMusic"] forState:UIControlStateNormal];
[self.navRightBtn setImage:[UIImage imageNamed:@"closeMusic"] forState:UIControlStateSelected];
[self.navRightBtn addTarget:self action:@selector(rightBtnClick:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.navRightBtn];
}
- (void)startBtnClick:(UIButton *)sender {
#pragma mark - Action
- (void)startRelaxAction:(UIButton *)sender {
sender.selected = !sender.selected;
self.rotateView.hidden = YES;
self.timeLb.hidden = YES;
self.minuteLb.hidden = YES;
self.starBtn.hidden = YES;
self.pickerView.hidden = YES;
self.startRelaxBtn.hidden = YES;
self.minutePickerView.hidden = YES;
[self.zoomView play];
self.zoomView.hidden = NO;
// self.allAccount = self.timeAccount * 60;
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerd:) userInfo:nil repeats:YES];
// [self.streamer play];
self.playDuration = self.minuteTime * 60;
self.controlTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[self.audioStreamer play];
}
- (void)rightBtnClick:(UIButton *)sender {
- (void)timerAction:(NSTimer *)timer {
self.playDuration--;
DSLog(@"playDuration:%ld", self.playDuration);
if (self.playDuration % 5 == 0 && self.playDuration >= 0) {
AudioServicesPlaySystemSound(1520);
}
if (self.playDuration == 0) {
self.zoomView.animationProgress = 0;
self.zoomView.hidden = YES;
self.rotateView.hidden = NO;
self.timeLb.hidden = NO;
self.minuteLb.hidden = NO;
self.startRelaxBtn.hidden = NO;
self.minutePickerView.hidden = NO;
[self cancelAudioStreamer];
}
}
- (void)volumeControlAction:(UIButton *)sender {
sender.selected = !sender.selected;
// self.streamer.volume = sender.selected ? 0 : 1;
self.audioStreamer.volume = sender.selected ? 0 : 1;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == kStatusKVOKey1) {
[self performSelector:@selector(updateStatus)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
} else if (context == @"idleTimerDisabled") {
if (![UIApplication sharedApplication].idleTimerDisabled) {
[UIApplication sharedApplication].idleTimerDisabled = YES;
}
} else {
// [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)updateStatus {
switch ([self.audioStreamer status]) {
case DOUAudioStreamerPlaying:
break;
case DOUAudioStreamerPaused:
[self.audioStreamer pause];
// [self pauseTimer];
break;
case DOUAudioStreamerIdle:
break;
case DOUAudioStreamerFinished:
DSLog(@"DOUAudioStreamerFinished");
[self.audioStreamer play];
break;
case DOUAudioStreamerBuffering:
break;
case DOUAudioStreamerError:
break;
}
}
#pragma mark - UIPickerViewDataSource && UIPickerViewDelegate
......@@ -133,11 +227,11 @@
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return self.dataArr.count;
return self.minuteDatas.count;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
self.timeAccount = [[self.dataArr objectAtIndex:row] integerValue];
self.minuteTime = [[self.minuteDatas objectAtIndex:row] integerValue];
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
......@@ -149,7 +243,7 @@
}
- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *titleString = [self.dataArr objectAtIndex:row];
NSString *titleString = [self.minuteDatas objectAtIndex:row];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:titleString];
NSRange range = [titleString rangeOfString:titleString];
[attributedString addAttribute:NSForegroundColorAttributeName value:BrandColor range:range];
......@@ -186,26 +280,38 @@
return _zoomView;
}
- (UIPickerView *)pickerView {
if (!_pickerView) {
_pickerView = [UIPickerView new];
_pickerView.delegate = self;
_pickerView.dataSource = self;
- (UIPickerView *)minutePickerView {
if (!_minutePickerView) {
_minutePickerView = [UIPickerView new];
_minutePickerView.delegate = self;
_minutePickerView.dataSource = self;
}
return _pickerView;
}
- (UIButton *)starBtn {
if (!_starBtn) {
_starBtn = [UIButton new];
_starBtn.layer.cornerRadius = 20;
_starBtn.layer.masksToBounds = YES;
[_starBtn addTarget:self action:@selector(startBtnClick:) forControlEvents:UIControlEventTouchUpInside];
_starBtn.titleLabel.font = [UIFont systemFontOfSize:16];
_starBtn.backgroundColor = BrandColor;
[_starBtn setTitle:@"开始放松" forState:UIControlStateNormal];
return _minutePickerView;
}
- (UIButton *)startRelaxBtn {
if (!_startRelaxBtn) {
_startRelaxBtn = [UIButton new];
_startRelaxBtn.layer.cornerRadius = 20;
_startRelaxBtn.layer.masksToBounds = YES;
[_startRelaxBtn addTarget:self action:@selector(startRelaxAction:) forControlEvents:UIControlEventTouchUpInside];
_startRelaxBtn.titleLabel.font = [UIFont systemFontOfSize:16];
_startRelaxBtn.backgroundColor = BrandColor;
[_startRelaxBtn setTitle:@"开始放松" forState:UIControlStateNormal];
}
return _startRelaxBtn;
}
static void *kStatusKVOKey1 = &kStatusKVOKey1;
- (DOUAudioStreamer *)audioStreamer {
if (!_audioStreamer) {
Track *track = [[Track alloc] init];
track.audioFileURL = self.audioFileURL;
_audioStreamer = [DOUAudioStreamer streamerWithAudioFile:track];
_audioStreamer.volume = 1;
[_audioStreamer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:kStatusKVOKey1];
}
return _starBtn;
return _audioStreamer;
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#import <DOUAudioFile.h>
@interface Track : NSObject <DOUAudioFile>
@property (nonatomic, strong) NSString *artist;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSURL *audioFileURL;
@property (nonatomic, assign) int playId;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "Track.h"
@implementation Track
@end
......@@ -7,6 +7,7 @@ target 'DreamSleep' do
pod 'MJRefresh', '~> 3.7.5'
pod 'Masonry', '~> 1.1.0'
pod 'lottie-ios', '~> 2.5.3'
pod 'DOUAudioStreamer', '~> 0.2.16'
end
# AFNetworking (4.0.1)
......@@ -15,3 +16,4 @@ end
# MJRefresh (3.7.5)
# Masonry (1.1.0)
# lottie-ios (2.5.3)
# DOUAudioStreamer (0.2.16)
......@@ -19,6 +19,7 @@ PODS:
- DKNightVersion/Core
- DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core
- DOUAudioStreamer (0.2.16)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MJRefresh (3.7.5)
......@@ -27,6 +28,7 @@ PODS:
DEPENDENCIES:
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MJRefresh (~> 3.7.5)
......@@ -36,6 +38,7 @@ SPEC REPOS:
trunk:
- AFNetworking
- DKNightVersion
- DOUAudioStreamer
- lottie-ios
- Masonry
- MJRefresh
......@@ -44,11 +47,12 @@ SPEC REPOS:
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
PODFILE CHECKSUM: da4f3494cbd800091ea38764f7f43754ca9369ea
PODFILE CHECKSUM: e7bb0ee0a6fff85de99e8ba732a1592a8a5ab9c4
COCOAPODS: 1.11.3
Copyright (c) 2013-2016, Douban Inc. <http://www.douban.com/>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the Douban Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# DOUAudioStreamer
[![CocoaPods](https://img.shields.io/cocoapods/v/DOUAudioStreamer.svg)](https://cocoapods.org/pods/DOUAudioStreamer)
[![Platform](https://img.shields.io/cocoapods/p/DOUAudioStreamer.svg)](https://cocoapods.org/pods/DOUAudioStreamer)
[![License](https://img.shields.io/cocoapods/l/DOUAudioStreamer.svg)](https://github.com/douban/DOUAudioStreamer/blob/master/LICENSE)
DOUAudioStreamer is a Core Audio based streaming audio player for iOS/Mac.
## How to Use
[Download](https://github.com/douban/DOUAudioStreamer/archive/master.zip) DOUAudioStreamer, drag everything inside src into your Xcode project and you are ready to go.
## Examples
A working demonstration is included inside [example](https://github.com/douban/DOUAudioStreamer/tree/master/example) folder.
The documentation for DOUAudioStreamer is coming.
## License
Use and distribution of licensed under the BSD license. See the [LICENSE](https://github.com/douban/DOUAudioStreamer/blob/master/LICENSE) file for full text.
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer.h"
@interface DOUAudioAnalyzer (Default)
+ (instancetype)spatialAnalyzer;
+ (instancetype)frequencyAnalyzer;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer+Default.h"
#import "DOUAudioSpatialAnalyzer.h"
#import "DOUAudioFrequencyAnalyzer.h"
@implementation DOUAudioAnalyzer (Default)
+ (instancetype)spatialAnalyzer
{
return [DOUAudioSpatialAnalyzer analyzer];
}
+ (instancetype)frequencyAnalyzer
{
return [DOUAudioFrequencyAnalyzer analyzer];
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#define kDOUAudioAnalyzerLevelCount 20
@interface DOUAudioAnalyzer : NSObject
+ (instancetype)analyzer;
- (void)handleLPCMSamples:(int16_t *)samples count:(NSUInteger)count;
- (void)flush;
- (void)copyLevels:(float *)levels;
@property (nonatomic, assign) NSTimeInterval interval;
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer.h"
#import "DOUAudioAnalyzer_Private.h"
#include <Accelerate/Accelerate.h>
#include <pthread.h>
#include <mach/mach_time.h>
@interface DOUAudioAnalyzer () {
@private
int16_t _sampleBuffer[kDOUAudioAnalyzerSampleCount];
struct {
float sample[kDOUAudioAnalyzerSampleCount];
float left[kDOUAudioAnalyzerCount];
float right[kDOUAudioAnalyzerCount];
} _vectors;
struct {
float left[kDOUAudioAnalyzerLevelCount];
float right[kDOUAudioAnalyzerLevelCount];
float overall[kDOUAudioAnalyzerLevelCount];
} _levels;
uint64_t _interval;
uint64_t _lastTime;
BOOL _enabled;
pthread_mutex_t _mutex;
}
@end
@implementation DOUAudioAnalyzer
@synthesize enabled = _enabled;
+ (instancetype)analyzer
{
return [[self alloc] init];
}
- (id)init
{
self = [super init];
if (self) {
_enabled = NO;
pthread_mutex_init(&_mutex, NULL);
_lastTime = 0;
[self setInterval:0.1];
[self flush];
}
return self;
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
}
- (void)handleLPCMSamples:(int16_t *)samples count:(NSUInteger)count
{
pthread_mutex_lock(&_mutex);
if (samples == NULL ||
count == 0) {
pthread_mutex_unlock(&_mutex);
return;
}
if (!_enabled) {
pthread_mutex_unlock(&_mutex);
return;
}
uint64_t currentTime = mach_absolute_time();
if (currentTime - _lastTime < _interval) {
pthread_mutex_unlock(&_mutex);
return;
}
else {
_lastTime = currentTime;
}
if (count >= kDOUAudioAnalyzerSampleCount) {
[self _analyzeLinearPCMSamples:samples];
}
else {
memcpy(_sampleBuffer, samples, sizeof(int16_t) * count);
memset(_sampleBuffer + count, 0, sizeof(int16_t) * (kDOUAudioAnalyzerSampleCount - count));
[self _analyzeLinearPCMSamples:_sampleBuffer];
}
pthread_mutex_unlock(&_mutex);
}
- (void)flush
{
pthread_mutex_lock(&_mutex);
vDSP_vclr(_levels.overall, 1, kDOUAudioAnalyzerLevelCount);
pthread_mutex_unlock(&_mutex);
}
+ (double)_absoluteTimeConversion
{
static double conversion;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mach_timebase_info_data_t info;
mach_timebase_info(&info);
conversion = 1.0e-9 * info.numer / info.denom;
});
return conversion;
}
- (NSTimeInterval)interval
{
return [[self class] _absoluteTimeConversion] * _interval;
}
- (void)setInterval:(NSTimeInterval)interval
{
pthread_mutex_lock(&_mutex);
_interval = (uint64_t)llrint(round(interval / [[self class] _absoluteTimeConversion]));
pthread_mutex_unlock(&_mutex);
}
- (void)setEnabled:(BOOL)enabled
{
if (_enabled != enabled) {
pthread_mutex_lock(&_mutex);
_enabled = enabled;
pthread_mutex_unlock(&_mutex);
}
}
- (void)copyLevels:(float *)levels
{
pthread_mutex_lock(&_mutex);
if (levels != NULL) {
memcpy(levels, _levels.overall, sizeof(float) * kDOUAudioAnalyzerLevelCount);
}
pthread_mutex_unlock(&_mutex);
}
- (void)_analyzeLinearPCMSamples:(const int16_t *)samples
{
[self _splitStereoSamples:samples];
[self processChannelVectors:_vectors.left toLevels:_levels.left];
[self processChannelVectors:_vectors.right toLevels:_levels.right];
[self _updateLevels];
}
- (void)_splitStereoSamples:(const int16_t *)samples
{
static const float scale = INT16_MAX;
vDSP_vflt16((int16_t *)samples, 1, _vectors.sample, 1, kDOUAudioAnalyzerSampleCount);
vDSP_vsdiv(_vectors.sample, 1, (float *)&scale, _vectors.sample, 1, kDOUAudioAnalyzerSampleCount);
DSPSplitComplex complexSplit;
complexSplit.realp = _vectors.left;
complexSplit.imagp = _vectors.right;
vDSP_ctoz((const DSPComplex *)_vectors.sample, 2, &complexSplit, 1, kDOUAudioAnalyzerCount);
}
- (void)_updateLevels
{
static const float scale = 2.0f;
vDSP_vadd(_levels.left, 1, _levels.right, 1, _levels.overall, 1, kDOUAudioAnalyzerLevelCount);
vDSP_vsdiv(_levels.overall, 1, (float *)&scale, _levels.overall, 1, kDOUAudioAnalyzerLevelCount);
static const float min = 0.0f;
static const float max = 1.0f;
vDSP_vclip(_levels.overall, 1, (float *)&min, (float *)&max, _levels.overall, 1, kDOUAudioAnalyzerLevelCount);
}
- (void)processChannelVectors:(const float *)vectors toLevels:(float *)levels
{
[self doesNotRecognizeSelector:_cmd];
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer.h"
#define kDOUAudioAnalyzerSampleCount 1024
#define kDOUAudioAnalyzerCount (kDOUAudioAnalyzerSampleCount / 2)
@interface DOUAudioAnalyzer ()
- (void)processChannelVectors:(const float *)vectors toLevels:(float *)levels;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#ifdef __cplusplus
#define DOUAS_EXTERN extern "C"
#else /* __cplusplus */
#define DOUAS_EXTERN extern
#endif /* __cplusplus */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#include <CoreAudio/CoreAudioTypes.h>
typedef NS_ENUM(NSUInteger, DOUAudioDecoderStatus) {
DOUAudioDecoderSucceeded,
DOUAudioDecoderFailed,
DOUAudioDecoderEndEncountered,
DOUAudioDecoderWaiting
};
@class DOUAudioPlaybackItem;
@class DOUAudioLPCM;
@interface DOUAudioDecoder : NSObject
+ (AudioStreamBasicDescription)defaultOutputFormat;
+ (instancetype)decoderWithPlaybackItem:(DOUAudioPlaybackItem *)playbackItem
bufferSize:(NSUInteger)bufferSize;
- (instancetype)initWithPlaybackItem:(DOUAudioPlaybackItem *)playbackItem
bufferSize:(NSUInteger)bufferSize;
- (BOOL)setUp;
- (void)tearDown;
- (DOUAudioDecoderStatus)decodeOnce;
- (void)seekToTime:(NSUInteger)milliseconds;
@property (nonatomic, readonly) DOUAudioPlaybackItem *playbackItem;
@property (nonatomic, readonly) DOUAudioLPCM *lpcm;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioDecoder.h"
#import "DOUAudioFileProvider.h"
#import "DOUAudioPlaybackItem.h"
#import "DOUAudioLPCM.h"
#include <AudioToolbox/AudioToolbox.h>
#include <pthread.h>
typedef struct {
AudioFileID afid;
SInt64 pos;
void *srcBuffer;
UInt32 srcBufferSize;
AudioStreamBasicDescription srcFormat;
UInt32 srcSizePerPacket;
UInt32 numPacketsPerRead;
AudioStreamPacketDescription *pktDescs;
} AudioFileIO;
typedef struct {
AudioStreamBasicDescription inputFormat;
AudioStreamBasicDescription outputFormat;
AudioFileIO afio;
SInt64 decodeValidFrames;
AudioStreamPacketDescription *outputPktDescs;
UInt32 outputBufferSize;
void *outputBuffer;
UInt32 numOutputPackets;
SInt64 outputPos;
pthread_mutex_t mutex;
} DecodingContext;
@interface DOUAudioDecoder () {
@private
DOUAudioPlaybackItem *_playbackItem;
DOUAudioLPCM *_lpcm;
AudioStreamBasicDescription _outputFormat;
AudioConverterRef _audioConverter;
NSUInteger _bufferSize;
DecodingContext _decodingContext;
BOOL _decodingContextInitialized;
}
@end
@implementation DOUAudioDecoder
@synthesize playbackItem = _playbackItem;
@synthesize lpcm = _lpcm;
+ (AudioStreamBasicDescription)defaultOutputFormat
{
static AudioStreamBasicDescription defaultOutputFormat;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultOutputFormat.mFormatID = kAudioFormatLinearPCM;
defaultOutputFormat.mSampleRate = 44100;
defaultOutputFormat.mBitsPerChannel = 16;
defaultOutputFormat.mChannelsPerFrame = 2;
defaultOutputFormat.mBytesPerFrame = defaultOutputFormat.mChannelsPerFrame * (defaultOutputFormat.mBitsPerChannel / 8);
defaultOutputFormat.mFramesPerPacket = 1;
defaultOutputFormat.mBytesPerPacket = defaultOutputFormat.mFramesPerPacket * defaultOutputFormat.mBytesPerFrame;
defaultOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
});
return defaultOutputFormat;
}
+ (instancetype)decoderWithPlaybackItem:(DOUAudioPlaybackItem *)playbackItem
bufferSize:(NSUInteger)bufferSize
{
return [[[self class] alloc] initWithPlaybackItem:playbackItem
bufferSize:bufferSize];
}
- (instancetype)initWithPlaybackItem:(DOUAudioPlaybackItem *)playbackItem
bufferSize:(NSUInteger)bufferSize
{
self = [super init];
if (self) {
_playbackItem = playbackItem;
_bufferSize = bufferSize;
_lpcm = [[DOUAudioLPCM alloc] init];
_outputFormat = [[self class] defaultOutputFormat];
[self _createAudioConverter];
if (_audioConverter == NULL) {
return nil;
}
}
return self;
}
- (void)dealloc
{
if (_decodingContextInitialized) {
[self tearDown];
}
if (_audioConverter != NULL) {
AudioConverterDispose(_audioConverter);
}
}
- (void)_createAudioConverter
{
AudioStreamBasicDescription inputFormat = [_playbackItem fileFormat];
OSStatus status = AudioConverterNew(&inputFormat, &_outputFormat, &_audioConverter);
if (status != noErr) {
_audioConverter = NULL;
}
}
- (void)_fillMagicCookieForAudioFileID:(AudioFileID)inputFile
{
UInt32 cookieSize = 0;
OSStatus status = AudioFileGetPropertyInfo(inputFile, kAudioFilePropertyMagicCookieData, &cookieSize, NULL);
if (status == noErr && cookieSize > 0) {
void *cookie = malloc(cookieSize);
status = AudioFileGetProperty(inputFile, kAudioFilePropertyMagicCookieData, &cookieSize, cookie);
if (status != noErr) {
free(cookie);
return;
}
status = AudioConverterSetProperty(_audioConverter, kAudioConverterDecompressionMagicCookie, cookieSize, cookie);
free(cookie);
if (status != noErr) {
return;
}
}
}
- (BOOL)setUp
{
if (_decodingContextInitialized) {
return YES;
}
AudioFileID inputFile = [_playbackItem fileID];
if (inputFile == NULL) {
return NO;
}
_decodingContext.inputFormat = [_playbackItem fileFormat];
_decodingContext.outputFormat = _outputFormat;
[self _fillMagicCookieForAudioFileID:inputFile];
UInt32 size;
OSStatus status;
size = sizeof(_decodingContext.inputFormat);
status = AudioConverterGetProperty(_audioConverter, kAudioConverterCurrentInputStreamDescription, &size, &_decodingContext.inputFormat);
if (status != noErr) {
return NO;
}
size = sizeof(_decodingContext.outputFormat);
status = AudioConverterGetProperty(_audioConverter, kAudioConverterCurrentOutputStreamDescription, &size, &_decodingContext.outputFormat);
if (status != noErr) {
return NO;
}
AudioStreamBasicDescription baseFormat;
UInt32 propertySize = sizeof(baseFormat);
AudioFileGetProperty(inputFile, kAudioFilePropertyDataFormat, &propertySize, &baseFormat);
double actualToBaseSampleRateRatio = 1.0;
if (_decodingContext.inputFormat.mSampleRate != baseFormat.mSampleRate &&
_decodingContext.inputFormat.mSampleRate != 0.0 &&
baseFormat.mSampleRate != 0.0) {
actualToBaseSampleRateRatio = _decodingContext.inputFormat.mSampleRate / baseFormat.mSampleRate;
}
double srcRatio = 1.0;
if (_decodingContext.outputFormat.mSampleRate != 0.0 &&
_decodingContext.inputFormat.mSampleRate != 0.0) {
srcRatio = _decodingContext.outputFormat.mSampleRate / _decodingContext.inputFormat.mSampleRate;
}
_decodingContext.decodeValidFrames = 0;
AudioFilePacketTableInfo srcPti;
if (_decodingContext.inputFormat.mBitsPerChannel == 0) {
size = sizeof(srcPti);
status = AudioFileGetProperty(inputFile, kAudioFilePropertyPacketTableInfo, &size, &srcPti);
if (status == noErr) {
_decodingContext.decodeValidFrames = (SInt64)(actualToBaseSampleRateRatio * srcRatio * srcPti.mNumberValidFrames + 0.5);
AudioConverterPrimeInfo primeInfo;
primeInfo.leadingFrames = (UInt32)(srcPti.mPrimingFrames * actualToBaseSampleRateRatio + 0.5);
primeInfo.trailingFrames = 0;
status = AudioConverterSetProperty(_audioConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo);
if (status != noErr) {
return NO;
}
}
}
_decodingContext.afio.afid = inputFile;
_decodingContext.afio.srcBufferSize = (UInt32)_bufferSize;
_decodingContext.afio.srcBuffer = malloc(_decodingContext.afio.srcBufferSize);
_decodingContext.afio.pos = 0;
_decodingContext.afio.srcFormat = _decodingContext.inputFormat;
if (_decodingContext.inputFormat.mBytesPerPacket == 0) {
size = sizeof(_decodingContext.afio.srcSizePerPacket);
status = AudioFileGetProperty(inputFile, kAudioFilePropertyPacketSizeUpperBound, &size, &_decodingContext.afio.srcSizePerPacket);
if (status != noErr) {
free(_decodingContext.afio.srcBuffer);
return NO;
}
_decodingContext.afio.numPacketsPerRead = _decodingContext.afio.srcBufferSize / _decodingContext.afio.srcSizePerPacket;
_decodingContext.afio.pktDescs = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * _decodingContext.afio.numPacketsPerRead);
}
else {
_decodingContext.afio.srcSizePerPacket = _decodingContext.inputFormat.mBytesPerPacket;
_decodingContext.afio.numPacketsPerRead = _decodingContext.afio.srcBufferSize / _decodingContext.afio.srcSizePerPacket;
_decodingContext.afio.pktDescs = NULL;
}
_decodingContext.outputPktDescs = NULL;
UInt32 outputSizePerPacket = _decodingContext.outputFormat.mBytesPerPacket;
_decodingContext.outputBufferSize = (UInt32)_bufferSize;
_decodingContext.outputBuffer = malloc(_decodingContext.outputBufferSize);
if (outputSizePerPacket == 0) {
size = sizeof(outputSizePerPacket);
status = AudioConverterGetProperty(_audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &outputSizePerPacket);
if (status != noErr) {
free(_decodingContext.afio.srcBuffer);
free(_decodingContext.outputBuffer);
if (_decodingContext.afio.pktDescs != NULL) {
free(_decodingContext.afio.pktDescs);
}
return NO;
}
_decodingContext.outputPktDescs = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * _decodingContext.outputBufferSize / outputSizePerPacket);
}
_decodingContext.numOutputPackets = _decodingContext.outputBufferSize / outputSizePerPacket;
_decodingContext.outputPos = 0;
pthread_mutex_init(&_decodingContext.mutex, NULL);
_decodingContextInitialized = YES;
return YES;
}
- (void)tearDown
{
if (!_decodingContextInitialized) {
return;
}
free(_decodingContext.afio.srcBuffer);
free(_decodingContext.outputBuffer);
if (_decodingContext.afio.pktDescs != NULL) {
free(_decodingContext.afio.pktDescs);
}
if (_decodingContext.outputPktDescs != NULL) {
free(_decodingContext.outputPktDescs);
}
pthread_mutex_destroy(&_decodingContext.mutex);
_decodingContextInitialized = NO;
}
static OSStatus decoder_data_proc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
AudioFileIO *afio = (AudioFileIO *)inUserData;
if (*ioNumberDataPackets > afio->numPacketsPerRead) {
*ioNumberDataPackets = afio->numPacketsPerRead;
}
UInt32 outNumBytes;
OSStatus status = AudioFileReadPackets(afio->afid, FALSE, &outNumBytes, afio->pktDescs, afio->pos, ioNumberDataPackets, afio->srcBuffer);
if (status != noErr) {
return status;
}
afio->pos += *ioNumberDataPackets;
ioData->mBuffers[0].mData = afio->srcBuffer;
ioData->mBuffers[0].mDataByteSize = outNumBytes;
ioData->mBuffers[0].mNumberChannels = afio->srcFormat.mChannelsPerFrame;
if (outDataPacketDescription != NULL) {
*outDataPacketDescription = afio->pktDescs;
}
return noErr;
}
- (DOUAudioDecoderStatus)decodeOnce
{
if (!_decodingContextInitialized) {
return DOUAudioDecoderFailed;
}
pthread_mutex_lock(&_decodingContext.mutex);
DOUAudioFileProvider *provider = [_playbackItem fileProvider];
if ([provider isFailed]) {
[_lpcm setEnd:YES];
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderFailed;
}
if (![provider isFinished]) {
NSUInteger dataOffset = [_playbackItem dataOffset];
NSUInteger expectedDataLength = [provider expectedLength];
NSInteger receivedDataLength = (NSInteger)([provider receivedLength] - dataOffset);
SInt64 packetNumber = _decodingContext.afio.pos + _decodingContext.afio.numPacketsPerRead;
SInt64 packetDataOffset = packetNumber * _decodingContext.afio.srcSizePerPacket;
SInt64 bytesPerPacket = _decodingContext.afio.srcSizePerPacket;
SInt64 bytesPerRead = bytesPerPacket * _decodingContext.afio.numPacketsPerRead;
SInt64 framesPerPacket = _decodingContext.inputFormat.mFramesPerPacket;
double intervalPerPacket = 1000.0 / _decodingContext.inputFormat.mSampleRate * framesPerPacket;
double intervalPerRead = intervalPerPacket / bytesPerPacket * bytesPerRead;
double downloadTime = 1000.0 * (bytesPerRead - (receivedDataLength - packetDataOffset)) / [provider downloadSpeed];
SInt64 bytesRemaining = (SInt64)(expectedDataLength - (NSUInteger)receivedDataLength);
if (receivedDataLength < packetDataOffset ||
(bytesRemaining > 0 &&
downloadTime > intervalPerRead)) {
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderWaiting;
}
}
AudioBufferList fillBufList;
fillBufList.mNumberBuffers = 1;
fillBufList.mBuffers[0].mNumberChannels = _decodingContext.inputFormat.mChannelsPerFrame;
fillBufList.mBuffers[0].mDataByteSize = _decodingContext.outputBufferSize;
fillBufList.mBuffers[0].mData = _decodingContext.outputBuffer;
OSStatus status;
UInt32 ioOutputDataPackets = _decodingContext.numOutputPackets;
status = AudioConverterFillComplexBuffer(_audioConverter, decoder_data_proc, &_decodingContext.afio, &ioOutputDataPackets, &fillBufList, _decodingContext.outputPktDescs);
if (status != noErr) {
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderFailed;
}
if (ioOutputDataPackets == 0) {
[_lpcm setEnd:YES];
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderEndEncountered;
}
SInt64 frame1 = _decodingContext.outputPos + ioOutputDataPackets;
if (_decodingContext.decodeValidFrames != 0 &&
frame1 > _decodingContext.decodeValidFrames) {
SInt64 framesToTrim64 = frame1 - _decodingContext.decodeValidFrames;
UInt32 framesToTrim = (framesToTrim64 > ioOutputDataPackets) ? ioOutputDataPackets : (UInt32)framesToTrim64;
int bytesToTrim = (int)(framesToTrim * _decodingContext.outputFormat.mBytesPerFrame);
fillBufList.mBuffers[0].mDataByteSize -= (unsigned long)bytesToTrim;
ioOutputDataPackets -= framesToTrim;
if (ioOutputDataPackets == 0) {
[_lpcm setEnd:YES];
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderEndEncountered;
}
}
UInt32 inNumBytes = fillBufList.mBuffers[0].mDataByteSize;
[_lpcm writeBytes:_decodingContext.outputBuffer length:inNumBytes];
_decodingContext.outputPos += ioOutputDataPackets;
pthread_mutex_unlock(&_decodingContext.mutex);
return DOUAudioDecoderSucceeded;
}
- (void)seekToTime:(NSUInteger)milliseconds
{
if (!_decodingContextInitialized) {
return;
}
pthread_mutex_lock(&_decodingContext.mutex);
double frames = (double)milliseconds * _decodingContext.inputFormat.mSampleRate / 1000.0;
double packets = frames / _decodingContext.inputFormat.mFramesPerPacket;
SInt64 packetNumebr = (SInt64)lrint(floor(packets));
_decodingContext.afio.pos = packetNumebr;
_decodingContext.outputPos = packetNumebr * _decodingContext.inputFormat.mFramesPerPacket / _decodingContext.outputFormat.mFramesPerPacket;
pthread_mutex_unlock(&_decodingContext.mutex);
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@class DOUAudioStreamer;
@interface DOUAudioEventLoop : NSObject
+ (instancetype)sharedEventLoop;
@property (nonatomic, strong) DOUAudioStreamer *currentStreamer;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) double volume;
@property (nonatomic, copy) NSArray *analyzers;
- (void)play;
- (void)pause;
- (void)stop;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioEventLoop.h"
#import "DOUAudioStreamer.h"
#import "DOUAudioStreamer_Private.h"
#import "DOUAudioStreamer+Options.h"
#import "DOUAudioFileProvider.h"
#import "DOUAudioPlaybackItem.h"
#import "DOUAudioLPCM.h"
#import "DOUAudioDecoder.h"
#import "DOUAudioRenderer.h"
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <pthread.h>
#include <sched.h>
typedef NS_ENUM(uint64_t, event_type) {
event_play,
event_pause,
event_stop,
event_seek,
event_streamer_changed,
event_provider_events,
event_finalizing,
#if TARGET_OS_IPHONE
event_interruption_begin,
event_interruption_end,
event_old_device_unavailable,
#endif /* TARGET_OS_IPHONE */
event_first = event_play,
#if TARGET_OS_IPHONE
event_last = event_old_device_unavailable,
#else /* TARGET_OS_IPHONE */
event_last = event_finalizing,
#endif /* TARGET_OS_IPHONE */
event_timeout
};
@interface DOUAudioEventLoop () {
@private
DOUAudioRenderer *_renderer;
DOUAudioStreamer *_currentStreamer;
NSUInteger _decoderBufferSize;
DOUAudioFileProviderEventBlock _fileProviderEventBlock;
int _kq;
void *_lastKQUserData;
pthread_mutex_t _mutex;
pthread_t _thread;
}
@end
@implementation DOUAudioEventLoop
@synthesize currentStreamer = _currentStreamer;
@dynamic analyzers;
+ (instancetype)sharedEventLoop
{
static DOUAudioEventLoop *sharedEventLoop = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedEventLoop = [[DOUAudioEventLoop alloc] init];
});
return sharedEventLoop;
}
- (instancetype)init
{
self = [super init];
if (self) {
_kq = kqueue();
pthread_mutex_init(&_mutex, NULL);
#if TARGET_OS_IPHONE
[self _setupAudioSession];
#endif /* TARGET_OS_IPHONE */
_renderer = [DOUAudioRenderer rendererWithBufferTime:kDOUAudioStreamerBufferTime];
[_renderer setUp];
if ([[NSUserDefaults standardUserDefaults] objectForKey:kDOUAudioStreamerVolumeKey] != nil) {
[self setVolume:[[NSUserDefaults standardUserDefaults] doubleForKey:kDOUAudioStreamerVolumeKey]];
}
else {
[self setVolume:1.0];
}
_decoderBufferSize = [[self class] _decoderBufferSize];
[self _setupFileProviderEventBlock];
[self _enableEvents];
[self _createThread];
}
return self;
}
- (void)dealloc
{
[self _sendEvent:event_finalizing];
pthread_join(_thread, NULL);
close(_kq);
pthread_mutex_destroy(&_mutex);
}
+ (NSUInteger)_decoderBufferSize
{
AudioStreamBasicDescription format = [DOUAudioDecoder defaultOutputFormat];
return kDOUAudioStreamerBufferTime * format.mSampleRate * format.mChannelsPerFrame * format.mBitsPerChannel / 8 / 1000;
}
#if TARGET_OS_IPHONE
- (void)_handleAudioSessionInterruptionWithState:(UInt32)state
{
if (state == kAudioSessionBeginInterruption) {
[_renderer setInterrupted:YES];
[_renderer stop];
[self _sendEvent:event_interruption_begin];
}
else if (state == kAudioSessionEndInterruption) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
AudioSessionInterruptionType interruptionType = kAudioSessionInterruptionType_ShouldNotResume;
UInt32 interruptionTypeSize = sizeof(interruptionType);
OSStatus status;
status = AudioSessionGetProperty(kAudioSessionProperty_InterruptionType,
&interruptionTypeSize,
&interruptionType);
NSAssert(status == noErr, @"failed to get interruption type");
#pragma clang diagnostic pop
[self _sendEvent:event_interruption_end
userData:(void *)(uintptr_t)interruptionType];
}
}
- (void)_handleAudioRouteChangeWithDictionary:(NSDictionary *)routeChangeDictionary
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
NSUInteger reason = [[routeChangeDictionary objectForKey:(__bridge NSString *)kAudioSession_RouteChangeKey_Reason] unsignedIntegerValue];
if (reason != kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
return;
}
NSDictionary *previousRouteDescription = [routeChangeDictionary objectForKey:(__bridge NSString *)kAudioSession_AudioRouteChangeKey_PreviousRouteDescription];
NSArray *previousOutputRoutes = [previousRouteDescription objectForKey:(__bridge NSString *)kAudioSession_AudioRouteKey_Outputs];
if ([previousOutputRoutes count] == 0) {
return;
}
NSString *previousOutputRouteType = [[previousOutputRoutes objectAtIndex:0] objectForKey:(__bridge NSString *)kAudioSession_AudioRouteKey_Type];
if (previousOutputRouteType == nil ||
(![previousOutputRouteType isEqualToString:(__bridge NSString *)kAudioSessionOutputRoute_Headphones] &&
![previousOutputRouteType isEqualToString:(__bridge NSString *)kAudioSessionOutputRoute_BluetoothA2DP])) {
return;
}
#pragma clang diagnostic pop
[self _sendEvent:event_old_device_unavailable];
}
static void audio_session_interruption_listener(void *inClientData, UInt32 inInterruptionState)
{
__unsafe_unretained DOUAudioEventLoop *eventLoop = (__bridge DOUAudioEventLoop *)inClientData;
[eventLoop _handleAudioSessionInterruptionWithState:inInterruptionState];
}
static void audio_route_change_listener(void *inClientData,
AudioSessionPropertyID inID,
UInt32 inDataSize,
const void *inData)
{
if (inID != kAudioSessionProperty_AudioRouteChange) {
return;
}
__unsafe_unretained DOUAudioEventLoop *eventLoop = (__bridge DOUAudioEventLoop *)inClientData;
[eventLoop _handleAudioRouteChangeWithDictionary:(__bridge NSDictionary *)inData];
}
- (void)_setupAudioSession
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
AudioSessionInitialize(NULL, NULL, audio_session_interruption_listener, (__bridge void *)self);
UInt32 audioCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory);
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audio_route_change_listener, (__bridge void *)self);
AudioSessionSetActive(TRUE);
#pragma clang diagnostic pop
}
#endif /* TARGET_OS_IPHONE */
- (void)_setupFileProviderEventBlock
{
__unsafe_unretained DOUAudioEventLoop *eventLoop = self;
_fileProviderEventBlock = ^{
[eventLoop _sendEvent:event_provider_events];
};
}
- (void)_enableEvents
{
for (uint64_t event = event_first; event <= event_last; ++event) {
struct kevent kev;
EV_SET(&kev, event, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, NULL);
kevent(_kq, &kev, 1, NULL, 0, NULL);
}
}
- (void)_sendEvent:(event_type)event
{
[self _sendEvent:event userData:NULL];
}
- (void)_sendEvent:(event_type)event userData:(void *)userData
{
struct kevent kev;
EV_SET(&kev, event, EVFILT_USER, 0, NOTE_TRIGGER, 0, userData);
kevent(_kq, &kev, 1, NULL, 0, NULL);
}
- (event_type)_waitForEvent
{
return [self _waitForEventWithTimeout:NSUIntegerMax];
}
- (event_type)_waitForEventWithTimeout:(NSUInteger)timeout
{
struct timespec _ts;
struct timespec *ts = NULL;
if (timeout != NSUIntegerMax) {
ts = &_ts;
ts->tv_sec = timeout / 1000;
ts->tv_nsec = (timeout % 1000) * 1000;
}
while (1) {
struct kevent kev;
int n = kevent(_kq, NULL, 0, &kev, 1, ts);
if (n > 0) {
if (kev.filter == EVFILT_USER &&
kev.ident >= event_first &&
kev.ident <= event_last) {
_lastKQUserData = kev.udata;
return kev.ident;
}
}
else {
break;
}
}
return event_timeout;
}
- (BOOL)_handleEvent:(event_type)event withStreamer:(DOUAudioStreamer **)streamer
{
if (event == event_play) {
if (*streamer != nil &&
([*streamer status] == DOUAudioStreamerPaused ||
[*streamer status] == DOUAudioStreamerIdle ||
[*streamer status] == DOUAudioStreamerFinished)) {
if ([_renderer isInterrupted]) {
#if TARGET_OS_IPHONE
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated"
const OSStatus status = AudioSessionSetActive(TRUE);
# pragma clang diagnostic pop
if (status == noErr) {
#endif /* TARGET_OS_IPHONE */
[*streamer setStatus:DOUAudioStreamerPlaying];
[_renderer setInterrupted:NO];
#if TARGET_OS_IPHONE
}
#endif /* TARGET_OS_IPHONE */
}
else {
[*streamer setStatus:DOUAudioStreamerPlaying];
}
}
}
else if (event == event_pause) {
if (*streamer != nil &&
([*streamer status] != DOUAudioStreamerPaused &&
[*streamer status] != DOUAudioStreamerIdle &&
[*streamer status] != DOUAudioStreamerFinished)) {
[_renderer stop];
[*streamer setStatus:DOUAudioStreamerPaused];
}
}
else if (event == event_stop) {
if (*streamer != nil &&
[*streamer status] != DOUAudioStreamerIdle) {
if ([*streamer status] != DOUAudioStreamerPaused) {
[_renderer stop];
}
[_renderer flush];
[*streamer setDecoder:nil];
[*streamer setPlaybackItem:nil];
[*streamer setStatus:DOUAudioStreamerIdle];
}
}
else if (event == event_seek) {
if (*streamer != nil &&
[*streamer decoder] != nil) {
NSUInteger milliseconds = MIN((NSUInteger)(uintptr_t)_lastKQUserData,
[[*streamer playbackItem] estimatedDuration]);
[*streamer setTimingOffset:(NSInteger)milliseconds - (NSInteger)[_renderer currentTime]];
[[*streamer decoder] seekToTime:milliseconds];
[_renderer flushShouldResetTiming:NO];
}
}
else if (event == event_streamer_changed) {
[_renderer stop];
[_renderer flush];
[[*streamer fileProvider] setEventBlock:NULL];
*streamer = _currentStreamer;
[[*streamer fileProvider] setEventBlock:_fileProviderEventBlock];
}
else if (event == event_provider_events) {
if (*streamer != nil &&
[*streamer status] == DOUAudioStreamerBuffering) {
[*streamer setStatus:DOUAudioStreamerPlaying];
}
[*streamer setBufferingRatio:(double)[[*streamer fileProvider] receivedLength] / [[*streamer fileProvider] expectedLength]];
}
else if (event == event_finalizing) {
return NO;
}
#if TARGET_OS_IPHONE
else if (event == event_interruption_begin) {
if (*streamer != nil &&
([*streamer status] != DOUAudioStreamerPaused &&
[*streamer status] != DOUAudioStreamerIdle &&
[*streamer status] != DOUAudioStreamerFinished)) {
[self performSelector:@selector(pause) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
[*streamer setPausedByInterruption:YES];
}
}
else if (event == event_interruption_end) {
const AudioSessionInterruptionType interruptionType = (AudioSessionInterruptionType)(uintptr_t)_lastKQUserData;
NSAssert(interruptionType == kAudioSessionInterruptionType_ShouldResume ||
interruptionType == kAudioSessionInterruptionType_ShouldNotResume,
@"invalid interruption type");
if (interruptionType == kAudioSessionInterruptionType_ShouldResume) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
OSStatus status;
status = AudioSessionSetActive(TRUE);
NSAssert(status == noErr, @"failed to activate audio session");
#pragma clang diagnostic pop
if (status == noErr) {
[_renderer setInterrupted:NO];
if (*streamer != nil &&
[*streamer status] == DOUAudioStreamerPaused &&
[*streamer isPausedByInterruption]) {
[*streamer setPausedByInterruption:NO];
[self performSelector:@selector(play) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
}
}
}
}
else if (event == event_old_device_unavailable) {
if (*streamer != nil) {
if ([*streamer status] != DOUAudioStreamerPaused &&
[*streamer status] != DOUAudioStreamerIdle &&
[*streamer status] != DOUAudioStreamerFinished) {
[self performSelector:@selector(pause)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
}
[*streamer setPausedByInterruption:NO];
}
}
#endif /* TARGET_OS_IPHONE */
else if (event == event_timeout) {
}
return YES;
}
- (void)_handleStreamer:(DOUAudioStreamer *)streamer
{
if (streamer == nil) {
return;
}
if ([streamer status] != DOUAudioStreamerPlaying) {
return;
}
if ([[streamer fileProvider] isFailed]) {
[streamer setError:[NSError errorWithDomain:kDOUAudioStreamerErrorDomain
code:DOUAudioStreamerNetworkError
userInfo:nil]];
[streamer setStatus:DOUAudioStreamerError];
return;
}
if (![[streamer fileProvider] isReady]) {
[streamer setStatus:DOUAudioStreamerBuffering];
return;
}
if ([streamer playbackItem] == nil) {
[streamer setPlaybackItem:[DOUAudioPlaybackItem playbackItemWithFileProvider:[streamer fileProvider]]];
if (![[streamer playbackItem] open]) {
[streamer setError:[NSError errorWithDomain:kDOUAudioStreamerErrorDomain
code:DOUAudioStreamerDecodingError
userInfo:nil]];
[streamer setStatus:DOUAudioStreamerError];
return;
}
[streamer setDuration:(NSTimeInterval)[[streamer playbackItem] estimatedDuration] / 1000.0];
}
if ([streamer decoder] == nil) {
[streamer setDecoder:[DOUAudioDecoder decoderWithPlaybackItem:[streamer playbackItem]
bufferSize:_decoderBufferSize]];
if (![[streamer decoder] setUp]) {
[streamer setError:[NSError errorWithDomain:kDOUAudioStreamerErrorDomain
code:DOUAudioStreamerDecodingError
userInfo:nil]];
[streamer setStatus:DOUAudioStreamerError];
return;
}
}
switch ([[streamer decoder] decodeOnce]) {
case DOUAudioDecoderSucceeded:
break;
case DOUAudioDecoderFailed:
[streamer setError:[NSError errorWithDomain:kDOUAudioStreamerErrorDomain
code:DOUAudioStreamerDecodingError
userInfo:nil]];
[streamer setStatus:DOUAudioStreamerError];
return;
case DOUAudioDecoderEndEncountered:
[_renderer stop];
[streamer setDecoder:nil];
[streamer setPlaybackItem:nil];
[streamer setStatus:DOUAudioStreamerFinished];
return;
case DOUAudioDecoderWaiting:
[streamer setStatus:DOUAudioStreamerBuffering];
return;
}
void *bytes = NULL;
NSUInteger length = 0;
[[[streamer decoder] lpcm] readBytes:&bytes length:&length];
if (bytes != NULL) {
[_renderer renderBytes:bytes length:length];
free(bytes);
}
}
- (void)_eventLoop
{
DOUAudioStreamer *streamer = nil;
while (1) {
@autoreleasepool {
if (streamer != nil) {
switch ([streamer status]) {
case DOUAudioStreamerPaused:
case DOUAudioStreamerIdle:
case DOUAudioStreamerFinished:
case DOUAudioStreamerBuffering:
case DOUAudioStreamerError:
if (![self _handleEvent:[self _waitForEvent]
withStreamer:&streamer]) {
return;
}
break;
default:
break;
}
}
else {
if (![self _handleEvent:[self _waitForEvent]
withStreamer:&streamer]) {
return;
}
}
if (![self _handleEvent:[self _waitForEventWithTimeout:0]
withStreamer:&streamer]) {
return;
}
if (streamer != nil) {
[self _handleStreamer:streamer];
}
}
}
}
static void *event_loop_main(void *info)
{
pthread_setname_np("com.douban.audio-streamer.event-loop");
__unsafe_unretained DOUAudioEventLoop *eventLoop = (__bridge DOUAudioEventLoop *)info;
@autoreleasepool {
[eventLoop _eventLoop];
}
return NULL;
}
- (void)_createThread
{
pthread_attr_t attr;
struct sched_param sched_param;
int sched_policy = SCHED_FIFO;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, sched_policy);
sched_param.sched_priority = sched_get_priority_max(sched_policy);
pthread_attr_setschedparam(&attr, &sched_param);
pthread_create(&_thread, &attr, event_loop_main, (__bridge void *)self);
pthread_attr_destroy(&attr);
}
- (void)setCurrentStreamer:(DOUAudioStreamer *)currentStreamer
{
if (_currentStreamer != currentStreamer) {
_currentStreamer = currentStreamer;
[self _sendEvent:event_streamer_changed];
}
}
- (NSTimeInterval)currentTime
{
return (NSTimeInterval)((NSUInteger)[[self currentStreamer] timingOffset] + [_renderer currentTime]) / 1000.0;
}
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
NSUInteger milliseconds = (NSUInteger)lrint(currentTime * 1000.0);
[self _sendEvent:event_seek userData:(void *)(uintptr_t)milliseconds];
}
- (double)volume
{
return [_renderer volume];
}
- (void)setVolume:(double)volume
{
[_renderer setVolume:volume];
if ([DOUAudioStreamer options] & DOUAudioStreamerKeepPersistentVolume) {
[[NSUserDefaults standardUserDefaults] setDouble:volume
forKey:kDOUAudioStreamerVolumeKey];
}
}
- (void)play
{
[self _sendEvent:event_play];
}
- (void)pause
{
[self _sendEvent:event_pause];
}
- (void)stop
{
[self _sendEvent:event_stop];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(analyzers) ||
aSelector == @selector(setAnalyzers:)) {
return _renderer;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@class DOUAudioFilePreprocessor;
@protocol DOUAudioFile <NSObject>
@required
- (NSURL *)audioFileURL;
@optional
- (NSString *)audioFileHost;
- (DOUAudioFilePreprocessor *)audioFilePreprocessor;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@interface DOUAudioFilePreprocessor : NSObject
- (NSData *)handleData:(NSData *)data offset:(NSUInteger)offset;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioFilePreprocessor.h"
@implementation DOUAudioFilePreprocessor
- (NSData *)handleData:(NSData *)data offset:(NSUInteger)offset
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#import "DOUAudioFile.h"
typedef void (^DOUAudioFileProviderEventBlock)(void);
@interface DOUAudioFileProvider : NSObject
+ (instancetype)fileProviderWithAudioFile:(id <DOUAudioFile>)audioFile;
+ (void)setHintWithAudioFile:(id <DOUAudioFile>)audioFile;
@property (nonatomic, readonly) id <DOUAudioFile> audioFile;
@property (nonatomic, copy) DOUAudioFileProviderEventBlock eventBlock;
@property (nonatomic, readonly) NSString *cachedPath;
@property (nonatomic, readonly) NSURL *cachedURL;
@property (nonatomic, readonly) NSString *mimeType;
@property (nonatomic, readonly) NSString *fileExtension;
@property (nonatomic, readonly) NSString *sha256;
@property (nonatomic, readonly) NSData *mappedData;
@property (nonatomic, readonly) NSUInteger expectedLength;
@property (nonatomic, readonly) NSUInteger receivedLength;
@property (nonatomic, readonly) NSUInteger downloadSpeed;
@property (nonatomic, readonly, getter=isFailed) BOOL failed;
@property (nonatomic, readonly, getter=isReady) BOOL ready;
@property (nonatomic, readonly, getter=isFinished) BOOL finished;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioFileProvider.h"
#import "DOUSimpleHTTPRequest.h"
#import "NSData+DOUAudioMappedFile.h"
#import "DOUAudioStreamer+Options.h"
#include <CommonCrypto/CommonDigest.h>
#include <AudioToolbox/AudioToolbox.h>
#if TARGET_OS_IPHONE
#include <MobileCoreServices/MobileCoreServices.h>
#else /* TARGET_OS_IPHONE */
#include <CoreServices/CoreServices.h>
#endif /* TARGET_OS_IPHONE */
#if TARGET_OS_IPHONE
#import "DOUMPMediaLibraryAssetLoader.h"
#endif /* TARGET_OS_IPHONE */
static id <DOUAudioFile> gHintFile = nil;
static DOUAudioFileProvider *gHintProvider = nil;
static BOOL gLastProviderIsFinished = NO;
@interface DOUAudioFileProvider () {
@protected
id <DOUAudioFile> _audioFile;
DOUAudioFileProviderEventBlock _eventBlock;
NSString *_cachedPath;
NSURL *_cachedURL;
NSString *_mimeType;
NSString *_fileExtension;
NSString *_sha256;
NSData *_mappedData;
NSUInteger _expectedLength;
NSUInteger _receivedLength;
BOOL _failed;
}
- (instancetype)_initWithAudioFile:(id <DOUAudioFile>)audioFile;
@end
@interface _DOUAudioLocalFileProvider : DOUAudioFileProvider
@end
@interface _DOUAudioRemoteFileProvider : DOUAudioFileProvider {
@private
DOUSimpleHTTPRequest *_request;
NSURL *_audioFileURL;
NSString *_audioFileHost;
CC_SHA256_CTX *_sha256Ctx;
AudioFileStreamID _audioFileStreamID;
BOOL _requiresCompleteFile;
BOOL _readyToProducePackets;
BOOL _requestCompleted;
}
@end
#if TARGET_OS_IPHONE
@interface _DOUAudioMediaLibraryFileProvider : DOUAudioFileProvider {
@private
DOUMPMediaLibraryAssetLoader *_assetLoader;
BOOL _loaderCompleted;
}
@end
#endif /* TARGET_OS_IPHONE */
#pragma mark - Concrete Audio Local File Provider
@implementation _DOUAudioLocalFileProvider
- (instancetype)_initWithAudioFile:(id <DOUAudioFile>)audioFile
{
self = [super _initWithAudioFile:audioFile];
if (self) {
_cachedURL = [audioFile audioFileURL];
_cachedPath = [_cachedURL path];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:_cachedPath
isDirectory:&isDirectory] ||
isDirectory) {
return nil;
}
_mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath];
_expectedLength = [_mappedData length];
_receivedLength = [_mappedData length];
}
return self;
}
- (NSString *)mimeType
{
if (_mimeType == nil &&
[self fileExtension] != nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[self fileExtension], NULL);
if (uti != NULL) {
_mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
CFRelease(uti);
}
}
return _mimeType;
}
- (NSString *)fileExtension
{
if (_fileExtension == nil) {
_fileExtension = [[[self audioFile] audioFileURL] pathExtension];
}
return _fileExtension;
}
- (NSString *)sha256
{
if (_sha256 == nil &&
[DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 &&
[self mappedData] != nil) {
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", hash[i]];
}
_sha256 = [result copy];
}
return _sha256;
}
- (NSUInteger)downloadSpeed
{
return _receivedLength;
}
- (BOOL)isReady
{
return YES;
}
- (BOOL)isFinished
{
return YES;
}
@end
#pragma mark - Concrete Audio Remote File Provider
@implementation _DOUAudioRemoteFileProvider
@synthesize finished = _requestCompleted;
- (instancetype)_initWithAudioFile:(id <DOUAudioFile>)audioFile
{
self = [super _initWithAudioFile:audioFile];
if (self) {
_audioFileURL = [audioFile audioFileURL];
if ([audioFile respondsToSelector:@selector(audioFileHost)]) {
_audioFileHost = [audioFile audioFileHost];
}
if ([DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256) {
_sha256Ctx = (CC_SHA256_CTX *)malloc(sizeof(CC_SHA256_CTX));
CC_SHA256_Init(_sha256Ctx);
}
[self _openAudioFileStream];
[self _createRequest];
[_request start];
}
return self;
}
- (void)dealloc
{
@synchronized(_request) {
[_request setCompletedBlock:NULL];
[_request setProgressBlock:NULL];
[_request setDidReceiveResponseBlock:NULL];
[_request setDidReceiveDataBlock:NULL];
[_request cancel];
}
if (_sha256Ctx != NULL) {
free(_sha256Ctx);
}
[self _closeAudioFileStream];
if ([DOUAudioStreamer options] & DOUAudioStreamerRemoveCacheOnDeallocation) {
[[NSFileManager defaultManager] removeItemAtPath:_cachedPath error:NULL];
}
}
+ (NSString *)_sha256ForAudioFileURL:(NSURL *)audioFileURL
{
NSString *string = [audioFileURL absoluteString];
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([string UTF8String], (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", hash[i]];
}
return result;
}
+ (NSString *)_cachedPathForAudioFileURL:(NSURL *)audioFileURL
{
NSString *filename = [NSString stringWithFormat:@"douas-%@.tmp", [self _sha256ForAudioFileURL:audioFileURL]];
return [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
}
- (void)_invokeEventBlock
{
if (_eventBlock != NULL) {
_eventBlock();
}
}
- (void)_requestDidComplete
{
if ([_request isFailed] ||
!([_request statusCode] >= 200 && [_request statusCode] < 300)
|| _receivedLength == 0) {
_failed = YES;
}
else {
_requestCompleted = YES;
[_mappedData dou_synchronizeMappedFile];
}
if (!_failed &&
_sha256Ctx != NULL) {
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(hash, _sha256Ctx);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", hash[i]];
}
_sha256 = [result copy];
}
if (gHintFile != nil &&
gHintProvider == nil) {
gHintProvider = [[[self class] alloc] _initWithAudioFile:gHintFile];
}
[self _invokeEventBlock];
}
- (void)_requestDidReportProgress:(double)progress
{
[self _invokeEventBlock];
}
- (void)_requestDidReceiveResponse
{
_expectedLength = [_request responseContentLength];
_cachedPath = [[self class] _cachedPathForAudioFileURL:_audioFileURL];
_cachedURL = [NSURL fileURLWithPath:_cachedPath];
[[NSFileManager defaultManager] createFileAtPath:_cachedPath contents:nil attributes:nil];
#if TARGET_OS_IPHONE
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone}
ofItemAtPath:_cachedPath
error:NULL];
#endif /* TARGET_OS_IPHONE */
[[NSFileHandle fileHandleForWritingAtPath:_cachedPath] truncateFileAtOffset:_expectedLength];
_mimeType = [[_request responseHeaders] objectForKey:@"Content-Type"];
_mappedData = [NSData dou_modifiableDataWithMappedContentsOfFile:_cachedPath];
}
- (void)_requestDidReceiveData:(NSData *)data
{
if (_mappedData == nil) {
return;
}
NSUInteger availableSpace = _expectedLength - _receivedLength;
NSUInteger bytesToWrite = MIN(availableSpace, [data length]);
memcpy((uint8_t *)[_mappedData bytes] + _receivedLength, [data bytes], bytesToWrite);
_receivedLength += bytesToWrite;
if (_sha256Ctx != NULL) {
CC_SHA256_Update(_sha256Ctx, [data bytes], (CC_LONG)[data length]);
}
if (!_readyToProducePackets && !_failed && !_requiresCompleteFile) {
OSStatus status = kAudioFileStreamError_UnsupportedFileType;
if (_audioFileStreamID != NULL) {
status = AudioFileStreamParseBytes(_audioFileStreamID,
(UInt32)[data length],
[data bytes],
0);
}
if (status != noErr && status != kAudioFileStreamError_NotOptimized) {
NSArray *fallbackTypeIDs = [self _fallbackTypeIDs];
for (NSNumber *typeIDNumber in fallbackTypeIDs) {
AudioFileTypeID typeID = (AudioFileTypeID)[typeIDNumber unsignedLongValue];
[self _closeAudioFileStream];
[self _openAudioFileStreamWithFileTypeHint:typeID];
if (_audioFileStreamID != NULL) {
status = AudioFileStreamParseBytes(_audioFileStreamID,
(UInt32)_receivedLength,
[_mappedData bytes],
0);
if (status == noErr || status == kAudioFileStreamError_NotOptimized) {
break;
}
}
}
if (status != noErr && status != kAudioFileStreamError_NotOptimized) {
_failed = YES;
}
}
if (status == kAudioFileStreamError_NotOptimized) {
[self _closeAudioFileStream];
_requiresCompleteFile = YES;
}
}
}
- (void)_createRequest
{
_request = [DOUSimpleHTTPRequest requestWithURL:_audioFileURL];
if (_audioFileHost != nil) {
[_request setHost:_audioFileHost];
}
__unsafe_unretained _DOUAudioRemoteFileProvider *_self = self;
[_request setCompletedBlock:^{
[_self _requestDidComplete];
}];
[_request setProgressBlock:^(double downloadProgress) {
[_self _requestDidReportProgress:downloadProgress];
}];
[_request setDidReceiveResponseBlock:^{
[_self _requestDidReceiveResponse];
}];
[_request setDidReceiveDataBlock:^(NSData *data) {
[_self _requestDidReceiveData:data];
}];
}
- (void)_handleAudioFileStreamProperty:(AudioFileStreamPropertyID)propertyID
{
if (propertyID == kAudioFileStreamProperty_ReadyToProducePackets) {
_readyToProducePackets = YES;
}
}
- (void)_handleAudioFileStreamPackets:(const void *)packets
numberOfBytes:(UInt32)numberOfBytes
numberOfPackets:(UInt32)numberOfPackets
packetDescriptions:(AudioStreamPacketDescription *)packetDescriptioins
{
}
static void audio_file_stream_property_listener_proc(void *inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 *ioFlags)
{
__unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData;
[fileProvider _handleAudioFileStreamProperty:inPropertyID];
}
static void audio_file_stream_packets_proc(void *inClientData,
UInt32 inNumberBytes,
UInt32 inNumberPackets,
const void *inInputData,
AudioStreamPacketDescription *inPacketDescriptions)
{
__unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData;
[fileProvider _handleAudioFileStreamPackets:inInputData
numberOfBytes:inNumberBytes
numberOfPackets:inNumberPackets
packetDescriptions:inPacketDescriptions];
}
- (void)_openAudioFileStream
{
[self _openAudioFileStreamWithFileTypeHint:0];
}
- (void)_openAudioFileStreamWithFileTypeHint:(AudioFileTypeID)fileTypeHint
{
OSStatus status = AudioFileStreamOpen((__bridge void *)self,
audio_file_stream_property_listener_proc,
audio_file_stream_packets_proc,
fileTypeHint,
&_audioFileStreamID);
if (status != noErr) {
_audioFileStreamID = NULL;
}
}
- (void)_closeAudioFileStream
{
if (_audioFileStreamID != NULL) {
AudioFileStreamClose(_audioFileStreamID);
_audioFileStreamID = NULL;
}
}
- (NSArray *)_fallbackTypeIDs
{
NSMutableArray *fallbackTypeIDs = [NSMutableArray array];
NSMutableSet *fallbackTypeIDSet = [NSMutableSet set];
struct {
CFStringRef specifier;
AudioFilePropertyID propertyID;
} properties[] = {
{ (__bridge CFStringRef)[self mimeType], kAudioFileGlobalInfo_TypesForMIMEType },
{ (__bridge CFStringRef)[self fileExtension], kAudioFileGlobalInfo_TypesForExtension }
};
const size_t numberOfProperties = sizeof(properties) / sizeof(properties[0]);
for (size_t i = 0; i < numberOfProperties; ++i) {
if (properties[i].specifier == NULL) {
continue;
}
UInt32 outSize = 0;
OSStatus status;
status = AudioFileGetGlobalInfoSize(properties[i].propertyID,
sizeof(properties[i].specifier),
&properties[i].specifier,
&outSize);
if (status != noErr) {
continue;
}
size_t count = outSize / sizeof(AudioFileTypeID);
AudioFileTypeID *buffer = (AudioFileTypeID *)malloc(outSize);
if (buffer == NULL) {
continue;
}
status = AudioFileGetGlobalInfo(properties[i].propertyID,
sizeof(properties[i].specifier),
&properties[i].specifier,
&outSize,
buffer);
if (status != noErr) {
free(buffer);
continue;
}
for (size_t j = 0; j < count; ++j) {
NSNumber *tid = [NSNumber numberWithUnsignedLong:buffer[j]];
if ([fallbackTypeIDSet containsObject:tid]) {
continue;
}
[fallbackTypeIDs addObject:tid];
[fallbackTypeIDSet addObject:tid];
}
free(buffer);
}
return fallbackTypeIDs;
}
- (NSString *)fileExtension
{
if (_fileExtension == nil) {
_fileExtension = [[[[self audioFile] audioFileURL] path] pathExtension];
}
return _fileExtension;
}
- (NSUInteger)downloadSpeed
{
return [_request downloadSpeed];
}
- (BOOL)isReady
{
if (!_requiresCompleteFile) {
return _readyToProducePackets;
}
return _requestCompleted;
}
@end
#pragma mark - Concrete Audio Media Library File Provider
#if TARGET_OS_IPHONE
@implementation _DOUAudioMediaLibraryFileProvider
- (instancetype)_initWithAudioFile:(id <DOUAudioFile>)audioFile
{
self = [super _initWithAudioFile:audioFile];
if (self) {
[self _createAssetLoader];
[_assetLoader start];
}
return self;
}
- (void)dealloc
{
@synchronized(_assetLoader) {
[_assetLoader setCompletedBlock:NULL];
[_assetLoader cancel];
}
[[NSFileManager defaultManager] removeItemAtPath:[_assetLoader cachedPath]
error:NULL];
}
- (void)_invokeEventBlock
{
if (_eventBlock != NULL) {
_eventBlock();
}
}
- (void)_assetLoaderDidComplete
{
if ([_assetLoader isFailed]) {
_failed = YES;
[self _invokeEventBlock];
return;
}
_mimeType = [_assetLoader mimeType];
_fileExtension = [_assetLoader fileExtension];
_cachedPath = [_assetLoader cachedPath];
_cachedURL = [NSURL fileURLWithPath:_cachedPath];
_mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath];
_expectedLength = [_mappedData length];
_receivedLength = [_mappedData length];
_loaderCompleted = YES;
[self _invokeEventBlock];
}
- (void)_createAssetLoader
{
_assetLoader = [DOUMPMediaLibraryAssetLoader loaderWithURL:[_audioFile audioFileURL]];
__weak typeof(self) weakSelf = self;
[_assetLoader setCompletedBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf _assetLoaderDidComplete];
}];
}
- (NSString *)sha256
{
if (_sha256 == nil &&
[DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 &&
[self mappedData] != nil) {
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", hash[i]];
}
_sha256 = [result copy];
}
return _sha256;
}
- (NSUInteger)downloadSpeed
{
return _receivedLength;
}
- (BOOL)isReady
{
return _loaderCompleted;
}
- (BOOL)isFinished
{
return _loaderCompleted;
}
@end
#endif /* TARGET_OS_IPHONE */
#pragma mark - Abstract Audio File Provider
@implementation DOUAudioFileProvider
@synthesize audioFile = _audioFile;
@synthesize eventBlock = _eventBlock;
@synthesize cachedPath = _cachedPath;
@synthesize cachedURL = _cachedURL;
@synthesize mimeType = _mimeType;
@synthesize fileExtension = _fileExtension;
@synthesize sha256 = _sha256;
@synthesize mappedData = _mappedData;
@synthesize expectedLength = _expectedLength;
@synthesize receivedLength = _receivedLength;
@synthesize failed = _failed;
+ (instancetype)_fileProviderWithAudioFile:(id <DOUAudioFile>)audioFile
{
if (audioFile == nil) {
return nil;
}
NSURL *audioFileURL = [audioFile audioFileURL];
if (audioFileURL == nil) {
return nil;
}
if ([audioFileURL isFileURL]) {
return [[_DOUAudioLocalFileProvider alloc] _initWithAudioFile:audioFile];
}
#if TARGET_OS_IPHONE
else if ([[audioFileURL scheme] isEqualToString:@"ipod-library"]) {
return [[_DOUAudioMediaLibraryFileProvider alloc] _initWithAudioFile:audioFile];
}
#endif /* TARGET_OS_IPHONE */
else {
return [[_DOUAudioRemoteFileProvider alloc] _initWithAudioFile:audioFile];
}
}
+ (instancetype)fileProviderWithAudioFile:(id <DOUAudioFile>)audioFile
{
if ((audioFile == gHintFile ||
[audioFile isEqual:gHintFile]) &&
gHintProvider != nil) {
DOUAudioFileProvider *provider = gHintProvider;
gHintFile = nil;
gHintProvider = nil;
gLastProviderIsFinished = [provider isFinished];
return provider;
}
gHintFile = nil;
gHintProvider = nil;
gLastProviderIsFinished = NO;
return [self _fileProviderWithAudioFile:audioFile];
}
+ (void)setHintWithAudioFile:(id <DOUAudioFile>)audioFile
{
if (audioFile == gHintFile ||
[audioFile isEqual:gHintFile]) {
return;
}
gHintFile = nil;
gHintProvider = nil;
if (audioFile == nil) {
return;
}
NSURL *audioFileURL = [audioFile audioFileURL];
if (audioFileURL == nil ||
#if TARGET_OS_IPHONE
[[audioFileURL scheme] isEqualToString:@"ipod-library"] ||
#endif /* TARGET_OS_IPHONE */
[audioFileURL isFileURL]) {
return;
}
gHintFile = audioFile;
if (gLastProviderIsFinished) {
gHintProvider = [self _fileProviderWithAudioFile:gHintFile];
}
}
- (instancetype)_initWithAudioFile:(id <DOUAudioFile>)audioFile
{
self = [super init];
if (self) {
_audioFile = audioFile;
}
return self;
}
- (NSUInteger)downloadSpeed
{
[self doesNotRecognizeSelector:_cmd];
return 0;
}
- (BOOL)isReady
{
[self doesNotRecognizeSelector:_cmd];
return NO;
}
- (BOOL)isFinished
{
[self doesNotRecognizeSelector:_cmd];
return NO;
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer.h"
@interface DOUAudioFrequencyAnalyzer : DOUAudioAnalyzer
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioFrequencyAnalyzer.h"
#import "DOUAudioAnalyzer_Private.h"
#include <Accelerate/Accelerate.h>
@interface DOUAudioFrequencyAnalyzer () {
@private
size_t _log2Count;
float _hammingWindow[kDOUAudioAnalyzerCount];
struct {
float real[kDOUAudioAnalyzerCount / 2];
float imag[kDOUAudioAnalyzerCount / 2];
} _complexSplitBuffer;
DSPSplitComplex _complexSplit;
FFTSetup _fft;
}
@end
@implementation DOUAudioFrequencyAnalyzer
- (id)init
{
self = [super init];
if (self) {
_log2Count = (size_t)lrintf(log2f(kDOUAudioAnalyzerCount));
vDSP_hamm_window(_hammingWindow, kDOUAudioAnalyzerCount, 0);
_complexSplit.realp = _complexSplitBuffer.real;
_complexSplit.imagp = _complexSplitBuffer.imag;
_fft = vDSP_create_fftsetup(_log2Count, kFFTRadix2);
}
return self;
}
- (void)dealloc
{
vDSP_destroy_fftsetup(_fft);
}
- (void)_splitInterleavedComplexVectors:(const float *)vectors
{
vDSP_vmul(vectors, 1, _hammingWindow, 1, (float *)vectors, 1, kDOUAudioAnalyzerCount);
vDSP_ctoz((const DSPComplex *)vectors, 2, &_complexSplit, 1, kDOUAudioAnalyzerCount / 2);
}
- (void)_performForwardDFTWithVectors:(const float *)vectors
{
vDSP_fft_zrip(_fft, &_complexSplit, 1, _log2Count, kFFTDirection_Forward);
vDSP_zvabs(&_complexSplit, 1, (float *)vectors, 1, kDOUAudioAnalyzerCount / 2);
static const float scale = 0.5f;
vDSP_vsmul(vectors, 1, &scale, (float *)vectors, 1, kDOUAudioAnalyzerCount / 2);
}
- (void)_normalizeVectors:(const float *)vectors toLevels:(float *)levels
{
static const int size = kDOUAudioAnalyzerCount / 4;
vDSP_vsq(vectors, 1, (float *)vectors, 1, size);
vvlog10f((float *)vectors, vectors, &size);
static const float multiplier = 1.0f / 16.0f;
const float increment = sqrtf(multiplier);
vDSP_vsmsa((float *)vectors, 1, (float *)&multiplier, (float *)&increment, (float *)vectors, 1, kDOUAudioAnalyzerCount / 2);
for (size_t i = 0; i < kDOUAudioAnalyzerLevelCount; ++i) {
levels[i] = vectors[1 + ((size - 1) / kDOUAudioAnalyzerLevelCount) * i];
}
}
- (void)processChannelVectors:(const float *)vectors toLevels:(float *)levels
{
[self _splitInterleavedComplexVectors:vectors];
[self _performForwardDFTWithVectors:vectors];
[self _normalizeVectors:vectors toLevels:levels];
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@interface DOUAudioLPCM : NSObject
@property (nonatomic, assign, getter=isEnd) BOOL end;
- (BOOL)readBytes:(void **)bytes length:(NSUInteger *)length;
- (void)writeBytes:(const void *)bytes length:(NSUInteger)length;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioLPCM.h"
#include <libkern/OSAtomic.h>
typedef struct data_segment {
void *bytes;
NSUInteger length;
struct data_segment *next;
} data_segment;
@interface DOUAudioLPCM () {
@private
data_segment *_segments;
BOOL _end;
OSSpinLock _lock;
}
@end
@implementation DOUAudioLPCM
@synthesize end = _end;
- (id)init
{
self = [super init];
if (self) {
_lock = OS_SPINLOCK_INIT;
}
return self;
}
- (void)dealloc
{
while (_segments != NULL) {
data_segment *next = _segments->next;
free(_segments);
_segments = next;
}
}
- (void)setEnd:(BOOL)end
{
OSSpinLockLock(&_lock);
if (end && !_end) {
_end = YES;
}
OSSpinLockUnlock(&_lock);
}
- (BOOL)readBytes:(void **)bytes length:(NSUInteger *)length
{
*bytes = NULL;
*length = 0;
OSSpinLockLock(&_lock);
if (_end && _segments == NULL) {
OSSpinLockUnlock(&_lock);
return NO;
}
if (_segments != NULL) {
*length = _segments->length;
*bytes = malloc(*length);
memcpy(*bytes, _segments->bytes, *length);
data_segment *next = _segments->next;
free(_segments);
_segments = next;
}
OSSpinLockUnlock(&_lock);
return YES;
}
- (void)writeBytes:(const void *)bytes length:(NSUInteger)length
{
OSSpinLockLock(&_lock);
if (_end) {
OSSpinLockUnlock(&_lock);
return;
}
if (bytes == NULL || length == 0) {
OSSpinLockUnlock(&_lock);
return;
}
data_segment *segment = (data_segment *)malloc(sizeof(data_segment) + length);
segment->bytes = segment + 1;
segment->length = length;
segment->next = NULL;
memcpy(segment->bytes, bytes, length);
data_segment **link = &_segments;
while (*link != NULL) {
data_segment *current = *link;
link = &current->next;
}
*link = segment;
OSSpinLockUnlock(&_lock);
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioToolbox/AudioToolbox.h>
@class DOUAudioFileProvider;
@class DOUAudioFilePreprocessor;
@protocol DOUAudioFile;
@interface DOUAudioPlaybackItem : NSObject
+ (instancetype)playbackItemWithFileProvider:(DOUAudioFileProvider *)fileProvider;
- (instancetype)initWithFileProvider:(DOUAudioFileProvider *)fileProvider;
@property (nonatomic, readonly) DOUAudioFileProvider *fileProvider;
@property (nonatomic, readonly) DOUAudioFilePreprocessor *filePreprocessor;
@property (nonatomic, readonly) id <DOUAudioFile> audioFile;
@property (nonatomic, readonly) NSURL *cachedURL;
@property (nonatomic, readonly) NSData *mappedData;
@property (nonatomic, readonly) AudioFileID fileID;
@property (nonatomic, readonly) AudioStreamBasicDescription fileFormat;
@property (nonatomic, readonly) NSUInteger bitRate;
@property (nonatomic, readonly) NSUInteger dataOffset;
@property (nonatomic, readonly) NSUInteger estimatedDuration;
@property (nonatomic, readonly, getter=isOpened) BOOL opened;
- (BOOL)open;
- (void)close;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioPlaybackItem.h"
#import "DOUAudioFileProvider.h"
#import "DOUAudioFilePreprocessor.h"
@interface DOUAudioPlaybackItem () {
@private
DOUAudioFileProvider *_fileProvider;
DOUAudioFilePreprocessor *_filePreprocessor;
AudioFileID _fileID;
AudioStreamBasicDescription _fileFormat;
NSUInteger _bitRate;
NSUInteger _dataOffset;
NSUInteger _estimatedDuration;
}
@end
@implementation DOUAudioPlaybackItem
@synthesize fileProvider = _fileProvider;
@synthesize filePreprocessor = _filePreprocessor;
@synthesize fileID = _fileID;
@synthesize fileFormat = _fileFormat;
@synthesize bitRate = _bitRate;
@synthesize dataOffset = _dataOffset;
@synthesize estimatedDuration = _estimatedDuration;
- (id <DOUAudioFile>)audioFile
{
return [_fileProvider audioFile];
}
- (NSURL *)cachedURL
{
return [_fileProvider cachedURL];
}
- (NSData *)mappedData
{
return [_fileProvider mappedData];
}
- (BOOL)isOpened
{
return _fileID != NULL;
}
static OSStatus audio_file_read(void *inClientData,
SInt64 inPosition,
UInt32 requestCount,
void *buffer,
UInt32 *actualCount)
{
__unsafe_unretained DOUAudioPlaybackItem *item = (__bridge DOUAudioPlaybackItem *)inClientData;
if (inPosition + requestCount > [[item mappedData] length]) {
if (inPosition >= [[item mappedData] length]) {
*actualCount = 0;
}
else {
*actualCount = (UInt32)((SInt64)[[item mappedData] length] - inPosition);
}
}
else {
*actualCount = requestCount;
}
if (*actualCount == 0) {
return noErr;
}
if ([item filePreprocessor] == nil) {
memcpy(buffer, (uint8_t *)[[item mappedData] bytes] + inPosition, *actualCount);
}
else {
NSData *input = [NSData dataWithBytesNoCopy:(uint8_t *)[[item mappedData] bytes] + inPosition
length:*actualCount
freeWhenDone:NO];
NSData *output = [[item filePreprocessor] handleData:input offset:(NSUInteger)inPosition];
memcpy(buffer, [output bytes], [output length]);
}
return noErr;
}
static SInt64 audio_file_get_size(void *inClientData)
{
__unsafe_unretained DOUAudioPlaybackItem *item = (__bridge DOUAudioPlaybackItem *)inClientData;
return (SInt64)[[item mappedData] length];
}
- (BOOL)_openWithFileTypeHint:(AudioFileTypeID)fileTypeHint
{
OSStatus status;
status = AudioFileOpenWithCallbacks((__bridge void *)self,
audio_file_read,
NULL,
audio_file_get_size,
NULL,
fileTypeHint,
&_fileID);
return status == noErr;
}
- (BOOL)_openWithFallbacks
{
NSArray *fallbackTypeIDs = [self _fallbackTypeIDs];
for (NSNumber *typeIDNumber in fallbackTypeIDs) {
AudioFileTypeID typeID = (AudioFileTypeID)[typeIDNumber unsignedLongValue];
if ([self _openWithFileTypeHint:typeID]) {
return YES;
}
}
return NO;
}
- (NSArray *)_fallbackTypeIDs
{
NSMutableArray *fallbackTypeIDs = [NSMutableArray array];
NSMutableSet *fallbackTypeIDSet = [NSMutableSet set];
struct {
CFStringRef specifier;
AudioFilePropertyID propertyID;
} properties[] = {
{ (__bridge CFStringRef)[_fileProvider mimeType], kAudioFileGlobalInfo_TypesForMIMEType },
{ (__bridge CFStringRef)[_fileProvider fileExtension], kAudioFileGlobalInfo_TypesForExtension }
};
const size_t numberOfProperties = sizeof(properties) / sizeof(properties[0]);
for (size_t i = 0; i < numberOfProperties; ++i) {
if (properties[i].specifier == NULL) {
continue;
}
UInt32 outSize = 0;
OSStatus status;
status = AudioFileGetGlobalInfoSize(properties[i].propertyID,
sizeof(properties[i].specifier),
&properties[i].specifier,
&outSize);
if (status != noErr) {
continue;
}
size_t count = outSize / sizeof(AudioFileTypeID);
AudioFileTypeID *buffer = (AudioFileTypeID *)malloc(outSize);
if (buffer == NULL) {
continue;
}
status = AudioFileGetGlobalInfo(properties[i].propertyID,
sizeof(properties[i].specifier),
&properties[i].specifier,
&outSize,
buffer);
if (status != noErr) {
free(buffer);
continue;
}
for (size_t j = 0; j < count; ++j) {
NSNumber *tid = [NSNumber numberWithUnsignedLong:buffer[j]];
if ([fallbackTypeIDSet containsObject:tid]) {
continue;
}
[fallbackTypeIDs addObject:tid];
[fallbackTypeIDSet addObject:tid];
}
free(buffer);
}
return fallbackTypeIDs;
}
- (BOOL)open
{
if ([self isOpened]) {
return YES;
}
if (![self _openWithFileTypeHint:0] &&
![self _openWithFallbacks]) {
_fileID = NULL;
return NO;
}
if (![self _fillFileFormat] ||
![self _fillMiscProperties]) {
AudioFileClose(_fileID);
_fileID = NULL;
return NO;
}
return YES;
}
- (BOOL)_fillFileFormat
{
UInt32 size;
OSStatus status;
status = AudioFileGetPropertyInfo(_fileID, kAudioFilePropertyFormatList, &size, NULL);
if (status != noErr) {
return NO;
}
UInt32 numFormats = size / sizeof(AudioFormatListItem);
AudioFormatListItem *formatList = (AudioFormatListItem *)malloc(size);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyFormatList, &size, formatList);
if (status != noErr) {
free(formatList);
return NO;
}
if (numFormats == 1) {
_fileFormat = formatList[0].mASBD;
}
else {
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size);
if (status != noErr) {
free(formatList);
return NO;
}
UInt32 numDecoders = size / sizeof(OSType);
OSType *decoderIDS = (OSType *)malloc(size);
status = AudioFormatGetProperty(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size, decoderIDS);
if (status != noErr) {
free(formatList);
free(decoderIDS);
return NO;
}
UInt32 i;
for (i = 0; i < numFormats; ++i) {
OSType decoderID = formatList[i].mASBD.mFormatID;
BOOL found = NO;
for (UInt32 j = 0; j < numDecoders; ++j) {
if (decoderID == decoderIDS[j]) {
found = YES;
break;
}
}
if (found) {
break;
}
}
free(decoderIDS);
if (i >= numFormats) {
free(formatList);
return NO;
}
_fileFormat = formatList[i].mASBD;
}
free(formatList);
return YES;
}
- (BOOL)_fillMiscProperties
{
UInt32 size;
OSStatus status;
UInt32 bitRate = 0;
size = sizeof(bitRate);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyBitRate, &size, &bitRate);
if (status != noErr) {
return NO;
}
_bitRate = bitRate;
SInt64 dataOffset = 0;
size = sizeof(dataOffset);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyDataOffset, &size, &dataOffset);
if (status != noErr) {
return NO;
}
_dataOffset = (NSUInteger)dataOffset;
Float64 estimatedDuration = 0.0;
size = sizeof(estimatedDuration);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyEstimatedDuration, &size, &estimatedDuration);
if (status != noErr) {
return NO;
}
_estimatedDuration = estimatedDuration * 1000.0;
return YES;
}
- (void)close
{
if (![self isOpened]) {
return;
}
AudioFileClose(_fileID);
_fileID = NULL;
}
+ (instancetype)playbackItemWithFileProvider:(DOUAudioFileProvider *)fileProvider
{
return [[[self class] alloc] initWithFileProvider:fileProvider];
}
- (instancetype)initWithFileProvider:(DOUAudioFileProvider *)fileProvider
{
self = [super init];
if (self) {
_fileProvider = fileProvider;
if ([[self audioFile] respondsToSelector:@selector(audioFilePreprocessor)]) {
_filePreprocessor = [[self audioFile] audioFilePreprocessor];
}
}
return self;
}
- (void)dealloc
{
if ([self isOpened]) {
[self close];
}
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@interface DOUAudioRenderer : NSObject
+ (instancetype)rendererWithBufferTime:(NSUInteger)bufferTime;
- (instancetype)initWithBufferTime:(NSUInteger)bufferTime;
- (BOOL)setUp;
- (void)tearDown;
- (void)renderBytes:(const void *)bytes length:(NSUInteger)length;
- (void)stop;
- (void)flush;
- (void)flushShouldResetTiming:(BOOL)shouldResetTiming;
@property (nonatomic, readonly) NSUInteger currentTime;
@property (nonatomic, readonly, getter=isStarted) BOOL started;
@property (nonatomic, assign, getter=isInterrupted) BOOL interrupted;
@property (nonatomic, assign) double volume;
@property (nonatomic, copy) NSArray *analyzers;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioRenderer.h"
#import "DOUAudioDecoder.h"
#import "DOUAudioAnalyzer.h"
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioUnit/AudioUnit.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/time.h>
#include <mach/mach_time.h>
#if !TARGET_OS_IPHONE
#include <CoreAudio/CoreAudio.h>
#endif /* !TARGET_OS_IPHONE */
#if TARGET_OS_IPHONE
#include <Accelerate/Accelerate.h>
#endif /* TARGET_OS_IPHONE */
@interface DOUAudioRenderer () {
@private
pthread_mutex_t _mutex;
pthread_cond_t _cond;
AudioComponentInstance _outputAudioUnit;
uint8_t *_buffer;
NSUInteger _bufferByteCount;
NSUInteger _firstValidByteOffset;
NSUInteger _validByteCount;
NSUInteger _bufferTime;
BOOL _started;
NSArray *_analyzers;
uint64_t _startedTime;
uint64_t _interruptedTime;
uint64_t _totalInterruptedInterval;
#if TARGET_OS_IPHONE
double _volume;
#endif /* TARGET_OS_IPHONE */
}
@end
@implementation DOUAudioRenderer
@synthesize started = _started;
@synthesize analyzers = _analyzers;
+ (instancetype)rendererWithBufferTime:(NSUInteger)bufferTime
{
return [[[self class] alloc] initWithBufferTime:bufferTime];
}
- (instancetype)initWithBufferTime:(NSUInteger)bufferTime
{
self = [super init];
if (self) {
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond, NULL);
_bufferTime = bufferTime;
#if TARGET_OS_IPHONE
_volume = 1.0;
#endif /* TARGET_OS_IPHONE */
#if !TARGET_OS_IPHONE
[self _setupPropertyListenerForDefaultOutputDevice];
#endif /* !TARGET_OS_IPHONE */
}
return self;
}
- (void)dealloc
{
#if !TARGET_OS_IPHONE
[self _removePropertyListenerForDefaultOutputDevice];
#endif /* !TARGET_OS_IPHONE */
if (_outputAudioUnit != NULL) {
[self tearDown];
}
if (_buffer != NULL) {
free(_buffer);
}
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
- (void)_setShouldInterceptTiming:(BOOL)shouldInterceptTiming
{
if (_startedTime == 0) {
_startedTime = mach_absolute_time();
}
if ((_interruptedTime != 0) == shouldInterceptTiming) {
return;
}
if (shouldInterceptTiming) {
_interruptedTime = mach_absolute_time();
}
else {
_totalInterruptedInterval += mach_absolute_time() - _interruptedTime;
_interruptedTime = 0;
}
}
static OSStatus au_render_callback(void *inRefCon,
AudioUnitRenderActionFlags *inActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
__unsafe_unretained DOUAudioRenderer *renderer = (__bridge DOUAudioRenderer *)inRefCon;
pthread_mutex_lock(&renderer->_mutex);
NSUInteger totalBytesToCopy = ioData->mBuffers[0].mDataByteSize;
NSUInteger validByteCount = renderer->_validByteCount;
if (validByteCount < totalBytesToCopy) {
[renderer->_analyzers makeObjectsPerformSelector:@selector(flush)];
[renderer _setShouldInterceptTiming:YES];
*inActionFlags = kAudioUnitRenderAction_OutputIsSilence;
bzero(ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
pthread_mutex_unlock(&renderer->_mutex);
return noErr;
}
else {
[renderer _setShouldInterceptTiming:NO];
}
uint8_t *bytes = renderer->_buffer + renderer->_firstValidByteOffset;
uint8_t *outBuffer = (uint8_t *)ioData->mBuffers[0].mData;
NSUInteger outBufSize = ioData->mBuffers[0].mDataByteSize;
NSUInteger bytesToCopy = MIN(outBufSize, validByteCount);
NSUInteger firstFrag = bytesToCopy;
if (renderer->_firstValidByteOffset + bytesToCopy > renderer->_bufferByteCount) {
firstFrag = renderer->_bufferByteCount - renderer->_firstValidByteOffset;
}
if (firstFrag < bytesToCopy) {
memcpy(outBuffer, bytes, firstFrag);
memcpy(outBuffer + firstFrag, renderer->_buffer, bytesToCopy - firstFrag);
}
else {
memcpy(outBuffer, bytes, bytesToCopy);
}
NSArray *analyzers = renderer->_analyzers;
if (analyzers != nil) {
for (DOUAudioAnalyzer *analyzer in analyzers) {
[analyzer handleLPCMSamples:(int16_t *)outBuffer
count:bytesToCopy / sizeof(int16_t)];
}
}
#if TARGET_OS_IPHONE
if (renderer->_volume != 1.0) {
int16_t *samples = (int16_t *)outBuffer;
size_t samplesCount = bytesToCopy / sizeof(int16_t);
float floatSamples[samplesCount];
vDSP_vflt16(samples, 1, floatSamples, 1, samplesCount);
float volume = renderer->_volume;
vDSP_vsmul(floatSamples, 1, &volume, floatSamples, 1, samplesCount);
vDSP_vfix16(floatSamples, 1, samples, 1, samplesCount);
}
#endif /* TARGET_OS_IPHONE */
if (bytesToCopy < outBufSize) {
bzero(outBuffer + bytesToCopy, outBufSize - bytesToCopy);
}
renderer->_validByteCount -= bytesToCopy;
renderer->_firstValidByteOffset = (renderer->_firstValidByteOffset + bytesToCopy) % renderer->_bufferByteCount;
pthread_mutex_unlock(&renderer->_mutex);
pthread_cond_signal(&renderer->_cond);
return noErr;
}
- (BOOL)setUp
{
if (_outputAudioUnit != NULL) {
return YES;
}
OSStatus status;
#if !TARGET_OS_IPHONE
CFRunLoopRef runLoop = NULL;
AudioObjectPropertyAddress address = {
kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, sizeof(runLoop), &runLoop);
if (status != noErr) {
return NO;
}
#endif /* !TARGET_OS_IPHONE */
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else /* TARGET_OS_IPHONE */
desc.componentSubType = kAudioUnitSubType_HALOutput;
#endif /* TARGET_OS_IPHONE */
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
if (comp == NULL) {
return NO;
}
status = AudioComponentInstanceNew(comp, &_outputAudioUnit);
if (status != noErr) {
_outputAudioUnit = NULL;
return NO;
}
AudioStreamBasicDescription requestedDesc = [DOUAudioDecoder defaultOutputFormat];
status = AudioUnitSetProperty(_outputAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &requestedDesc, sizeof(requestedDesc));
if (status != noErr) {
AudioComponentInstanceDispose(_outputAudioUnit);
_outputAudioUnit = NULL;
return NO;
}
UInt32 size = sizeof(requestedDesc);
status = AudioUnitGetProperty(_outputAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &requestedDesc, &size);
if (status != noErr) {
AudioComponentInstanceDispose(_outputAudioUnit);
_outputAudioUnit = NULL;
return NO;
}
AURenderCallbackStruct input;
input.inputProc = au_render_callback;
input.inputProcRefCon = (__bridge void *)self;
status = AudioUnitSetProperty(_outputAudioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input));
if (status != noErr) {
AudioComponentInstanceDispose(_outputAudioUnit);
_outputAudioUnit = NULL;
return NO;
}
status = AudioUnitInitialize(_outputAudioUnit);
if (status != noErr) {
AudioComponentInstanceDispose(_outputAudioUnit);
_outputAudioUnit = NULL;
return NO;
}
if (_buffer == NULL) {
_bufferByteCount = (_bufferTime * requestedDesc.mSampleRate / 1000) * (requestedDesc.mChannelsPerFrame * requestedDesc.mBitsPerChannel / 8);
_firstValidByteOffset = 0;
_validByteCount = 0;
_buffer = (uint8_t *)calloc(1, _bufferByteCount);
}
return YES;
}
- (void)tearDown
{
if (_outputAudioUnit == NULL) {
return;
}
[self stop];
[self _tearDownWithoutStop];
}
- (void)_tearDownWithoutStop
{
AudioUnitUninitialize(_outputAudioUnit);
AudioComponentInstanceDispose(_outputAudioUnit);
_outputAudioUnit = NULL;
}
#if !TARGET_OS_IPHONE
+ (const AudioObjectPropertyAddress *)_propertyListenerAddressForDefaultOutputDevice
{
static AudioObjectPropertyAddress address;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
address.mScope = kAudioObjectPropertyScopeGlobal;
address.mElement = kAudioObjectPropertyElementMaster;
});
return &address;
}
- (void)_handlePropertyListenerForDefaultOutputDevice
{
if (_outputAudioUnit == NULL) {
return;
}
BOOL started = _started;
[self stop];
pthread_mutex_lock(&_mutex);
[self _tearDownWithoutStop];
[self setUp];
if (started) {
AudioOutputUnitStart(_outputAudioUnit);
_started = YES;
}
pthread_mutex_unlock(&_mutex);
}
static OSStatus property_listener_default_output_device(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress inAddresses[],
void *inClientData)
{
__unsafe_unretained DOUAudioRenderer *renderer = (__bridge DOUAudioRenderer *)inClientData;
[renderer _handlePropertyListenerForDefaultOutputDevice];
return noErr;
}
- (void)_setupPropertyListenerForDefaultOutputDevice
{
AudioObjectAddPropertyListener(kAudioObjectSystemObject,
[[self class] _propertyListenerAddressForDefaultOutputDevice],
property_listener_default_output_device,
(__bridge void *)self);
}
- (void)_removePropertyListenerForDefaultOutputDevice
{
AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
[[self class] _propertyListenerAddressForDefaultOutputDevice],
property_listener_default_output_device,
(__bridge void *)self);
}
#endif /* !TARGET_OS_IPHONE */
- (void)renderBytes:(const void *)bytes length:(NSUInteger)length
{
if (_outputAudioUnit == NULL) {
return;
}
while (length > 0) {
pthread_mutex_lock(&_mutex);
NSUInteger emptyByteCount = _bufferByteCount - _validByteCount;
while (emptyByteCount == 0) {
if (!_started) {
if (_interrupted) {
pthread_mutex_unlock(&_mutex);
return;
}
pthread_mutex_unlock(&_mutex);
AudioOutputUnitStart(_outputAudioUnit);
pthread_mutex_lock(&_mutex);
_started = YES;
}
struct timeval tv;
struct timespec ts;
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec + 1;
ts.tv_nsec = 0;
pthread_cond_timedwait(&_cond, &_mutex, &ts);
emptyByteCount = _bufferByteCount - _validByteCount;
}
NSUInteger firstEmptyByteOffset = (_firstValidByteOffset + _validByteCount) % _bufferByteCount;
NSUInteger bytesToCopy;
if (firstEmptyByteOffset + emptyByteCount > _bufferByteCount) {
bytesToCopy = MIN(length, _bufferByteCount - firstEmptyByteOffset);
}
else {
bytesToCopy = MIN(length, emptyByteCount);
}
memcpy(_buffer + firstEmptyByteOffset, bytes, bytesToCopy);
length -= bytesToCopy;
bytes = (const uint8_t *)bytes + bytesToCopy;
_validByteCount += bytesToCopy;
pthread_mutex_unlock(&_mutex);
}
}
- (void)stop
{
[_analyzers makeObjectsPerformSelector:@selector(flush)];
if (_outputAudioUnit == NULL) {
return;
}
pthread_mutex_lock(&_mutex);
if (_started) {
pthread_mutex_unlock(&_mutex);
AudioOutputUnitStop(_outputAudioUnit);
pthread_mutex_lock(&_mutex);
[self _setShouldInterceptTiming:YES];
_started = NO;
}
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_cond);
}
- (void)flush
{
[self flushShouldResetTiming:YES];
}
- (void)flushShouldResetTiming:(BOOL)shouldResetTiming
{
[_analyzers makeObjectsPerformSelector:@selector(flush)];
if (_outputAudioUnit == NULL) {
return;
}
pthread_mutex_lock(&_mutex);
_firstValidByteOffset = 0;
_validByteCount = 0;
if (shouldResetTiming) {
[self _resetTiming];
}
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_cond);
}
+ (double)_absoluteTimeConversion
{
static double conversion;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mach_timebase_info_data_t info;
mach_timebase_info(&info);
conversion = 1.0e-9 * info.numer / info.denom;
});
return conversion;
}
- (void)_resetTiming
{
_startedTime = 0;
_interruptedTime = 0;
_totalInterruptedInterval = 0;
}
- (NSUInteger)currentTime
{
if (_startedTime == 0) {
return 0;
}
double base = [[self class] _absoluteTimeConversion] * 1000.0;
uint64_t interval;
if (_interruptedTime == 0) {
interval = mach_absolute_time() - _startedTime - _totalInterruptedInterval;
}
else {
interval = _interruptedTime - _startedTime - _totalInterruptedInterval;
}
return base * interval;
}
- (void)setInterrupted:(BOOL)interrupted
{
pthread_mutex_lock(&_mutex);
_interrupted = interrupted;
pthread_mutex_unlock(&_mutex);
}
- (double)volume
{
#if TARGET_OS_IPHONE
return _volume;
#else /* TARGET_OS_IPHONE */
if (_outputAudioUnit == NULL) {
return 0.0;
}
AudioUnitParameterValue volume = 0.0;
AudioUnitGetParameter(_outputAudioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 1, &volume);
return volume;
#endif /* TARGET_OS_IPHONE */
}
- (void)setVolume:(double)volume
{
#if TARGET_OS_IPHONE
_volume = volume;
#else /* TARGET_OS_IPHONE */
if (_outputAudioUnit == NULL) {
return;
}
volume = fmin(fmax(volume, 0.0), 1.0);
AudioUnitSetParameter(_outputAudioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 1, volume, 0);
#endif /* TARGET_OS_IPHONE */
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioAnalyzer.h"
@interface DOUAudioSpatialAnalyzer : DOUAudioAnalyzer
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioSpatialAnalyzer.h"
#import "DOUAudioAnalyzer_Private.h"
@implementation DOUAudioSpatialAnalyzer
- (void)processChannelVectors:(const float *)vectors toLevels:(float *)levels
{
for (size_t i = 0; i < kDOUAudioAnalyzerLevelCount; ++i) {
levels[i] = vectors[kDOUAudioAnalyzerCount * i / kDOUAudioAnalyzerLevelCount];
}
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioStreamer.h"
DOUAS_EXTERN NSString *const kDOUAudioStreamerVolumeKey;
DOUAS_EXTERN const NSUInteger kDOUAudioStreamerBufferTime;
typedef NS_OPTIONS(NSUInteger, DOUAudioStreamerOptions) {
DOUAudioStreamerKeepPersistentVolume = 1 << 0,
DOUAudioStreamerRemoveCacheOnDeallocation = 1 << 1,
DOUAudioStreamerRequireSHA256 = 1 << 2,
DOUAudioStreamerDefaultOptions = DOUAudioStreamerKeepPersistentVolume |
DOUAudioStreamerRemoveCacheOnDeallocation
};
@interface DOUAudioStreamer (Options)
+ (DOUAudioStreamerOptions)options;
+ (void)setOptions:(DOUAudioStreamerOptions)options;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioStreamer+Options.h"
NSString *const kDOUAudioStreamerVolumeKey = @"DOUAudioStreamerVolume";
const NSUInteger kDOUAudioStreamerBufferTime = 200;
static DOUAudioStreamerOptions gOptions = DOUAudioStreamerDefaultOptions;
@implementation DOUAudioStreamer (Options)
+ (DOUAudioStreamerOptions)options
{
return gOptions;
}
+ (void)setOptions:(DOUAudioStreamerOptions)options
{
if (!!((gOptions ^ options) & DOUAudioStreamerKeepPersistentVolume) &&
!(options & DOUAudioStreamerKeepPersistentVolume)) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kDOUAudioStreamerVolumeKey];
}
gOptions = options;
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
#import "DOUAudioBase.h"
#import "DOUAudioFile.h"
#import "DOUAudioFilePreprocessor.h"
#import "DOUAudioAnalyzer+Default.h"
DOUAS_EXTERN NSString *const kDOUAudioStreamerErrorDomain;
typedef NS_ENUM(NSUInteger, DOUAudioStreamerStatus) {
DOUAudioStreamerPlaying,
DOUAudioStreamerPaused,
DOUAudioStreamerIdle,
DOUAudioStreamerFinished,
DOUAudioStreamerBuffering,
DOUAudioStreamerError
};
typedef NS_ENUM(NSInteger, DOUAudioStreamerErrorCode) {
DOUAudioStreamerNetworkError,
DOUAudioStreamerDecodingError
};
@interface DOUAudioStreamer : NSObject
+ (instancetype)streamerWithAudioFile:(id <DOUAudioFile>)audioFile;
- (instancetype)initWithAudioFile:(id <DOUAudioFile>)audioFile;
+ (double)volume;
+ (void)setVolume:(double)volume;
+ (NSArray *)analyzers;
+ (void)setAnalyzers:(NSArray *)analyzers;
+ (void)setHintWithAudioFile:(id <DOUAudioFile>)audioFile;
@property (assign, readonly) DOUAudioStreamerStatus status;
@property (strong, readonly) NSError *error;
@property (nonatomic, readonly) id <DOUAudioFile> audioFile;
@property (nonatomic, readonly) NSURL *url;
@property (nonatomic, assign, readonly) NSTimeInterval duration;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) double volume;
@property (nonatomic, copy) NSArray *analyzers;
@property (nonatomic, readonly) NSString *cachedPath;
@property (nonatomic, readonly) NSURL *cachedURL;
@property (nonatomic, readonly) NSString *sha256;
@property (nonatomic, readonly) NSUInteger expectedLength;
@property (nonatomic, readonly) NSUInteger receivedLength;
@property (nonatomic, readonly) NSUInteger downloadSpeed;
@property (nonatomic, assign, readonly) double bufferingRatio;
- (void)play;
- (void)pause;
- (void)stop;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioStreamer.h"
#import "DOUAudioStreamer_Private.h"
#import "DOUAudioFileProvider.h"
#import "DOUAudioEventLoop.h"
NSString *const kDOUAudioStreamerErrorDomain = @"com.douban.audio-streamer.error-domain";
@interface DOUAudioStreamer () {
@private
id <DOUAudioFile> _audioFile;
DOUAudioStreamerStatus _status;
NSError *_error;
NSTimeInterval _duration;
NSInteger _timingOffset;
DOUAudioFileProvider *_fileProvider;
DOUAudioPlaybackItem *_playbackItem;
DOUAudioDecoder *_decoder;
double _bufferingRatio;
#if TARGET_OS_IPHONE
BOOL _pausedByInterruption;
#endif /* TARGET_OS_IPHONE */
}
@end
@implementation DOUAudioStreamer
@synthesize status = _status;
@synthesize error = _error;
@synthesize duration = _duration;
@synthesize timingOffset = _timingOffset;
@synthesize fileProvider = _fileProvider;
@synthesize playbackItem = _playbackItem;
@synthesize decoder = _decoder;
@synthesize bufferingRatio = _bufferingRatio;
#if TARGET_OS_IPHONE
@synthesize pausedByInterruption = _pausedByInterruption;
#endif /* TARGET_OS_IPHONE */
+ (instancetype)streamerWithAudioFile:(id <DOUAudioFile>)audioFile
{
return [[[self class] alloc] initWithAudioFile:audioFile];
}
- (instancetype)initWithAudioFile:(id <DOUAudioFile>)audioFile
{
self = [super init];
if (self) {
_audioFile = audioFile;
_status = DOUAudioStreamerIdle;
_fileProvider = [DOUAudioFileProvider fileProviderWithAudioFile:_audioFile];
if (_fileProvider == nil) {
return nil;
}
_bufferingRatio = (double)[_fileProvider receivedLength] / [_fileProvider expectedLength];
}
return self;
}
+ (double)volume
{
return [[DOUAudioEventLoop sharedEventLoop] volume];
}
+ (void)setVolume:(double)volume
{
[[DOUAudioEventLoop sharedEventLoop] setVolume:volume];
}
+ (NSArray *)analyzers
{
return [[DOUAudioEventLoop sharedEventLoop] analyzers];
}
+ (void)setAnalyzers:(NSArray *)analyzers
{
[[DOUAudioEventLoop sharedEventLoop] setAnalyzers:analyzers];
}
+ (void)setHintWithAudioFile:(id <DOUAudioFile>)audioFile
{
[DOUAudioFileProvider setHintWithAudioFile:audioFile];
}
- (id <DOUAudioFile>)audioFile
{
return _audioFile;
}
- (NSURL *)url
{
return [_audioFile audioFileURL];
}
- (NSTimeInterval)currentTime
{
if ([[DOUAudioEventLoop sharedEventLoop] currentStreamer] != self) {
return 0.0;
}
return [[DOUAudioEventLoop sharedEventLoop] currentTime];
}
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
if ([[DOUAudioEventLoop sharedEventLoop] currentStreamer] != self) {
return;
}
[[DOUAudioEventLoop sharedEventLoop] setCurrentTime:currentTime];
}
- (double)volume
{
return [[self class] volume];
}
- (void)setVolume:(double)volume
{
[[self class] setVolume:volume];
}
- (NSArray *)analyzers
{
return [[self class] analyzers];
}
- (void)setAnalyzers:(NSArray *)analyzers
{
[[self class] setAnalyzers:analyzers];
}
- (NSString *)cachedPath
{
return [_fileProvider cachedPath];
}
- (NSURL *)cachedURL
{
return [_fileProvider cachedURL];
}
- (NSString *)sha256
{
return [_fileProvider sha256];
}
- (NSUInteger)expectedLength
{
return [_fileProvider expectedLength];
}
- (NSUInteger)receivedLength
{
return [_fileProvider receivedLength];
}
- (NSUInteger)downloadSpeed
{
return [_fileProvider downloadSpeed];
}
- (void)play
{
@synchronized(self) {
if (_status != DOUAudioStreamerPaused &&
_status != DOUAudioStreamerIdle &&
_status != DOUAudioStreamerFinished) {
return;
}
if ([[DOUAudioEventLoop sharedEventLoop] currentStreamer] != self) {
[[DOUAudioEventLoop sharedEventLoop] pause];
[[DOUAudioEventLoop sharedEventLoop] setCurrentStreamer:self];
}
[[DOUAudioEventLoop sharedEventLoop] play];
}
}
- (void)pause
{
@synchronized(self) {
if (_status == DOUAudioStreamerPaused ||
_status == DOUAudioStreamerIdle ||
_status == DOUAudioStreamerFinished) {
return;
}
if ([[DOUAudioEventLoop sharedEventLoop] currentStreamer] != self) {
return;
}
[[DOUAudioEventLoop sharedEventLoop] pause];
}
}
- (void)stop
{
@synchronized(self) {
if (_status == DOUAudioStreamerIdle) {
return;
}
if ([[DOUAudioEventLoop sharedEventLoop] currentStreamer] != self) {
return;
}
[[DOUAudioEventLoop sharedEventLoop] stop];
[[DOUAudioEventLoop sharedEventLoop] setCurrentStreamer:nil];
}
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUAudioStreamer.h"
@class DOUAudioFileProvider;
@class DOUAudioPlaybackItem;
@class DOUAudioDecoder;
@interface DOUAudioStreamer ()
@property (assign) DOUAudioStreamerStatus status;
@property (strong) NSError *error;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) NSInteger timingOffset;
@property (nonatomic, readonly) DOUAudioFileProvider *fileProvider;
@property (nonatomic, strong) DOUAudioPlaybackItem *playbackItem;
@property (nonatomic, strong) DOUAudioDecoder *decoder;
@property (nonatomic, assign) double bufferingRatio;
#if TARGET_OS_IPHONE
@property (nonatomic, assign, getter=isPausedByInterruption) BOOL pausedByInterruption;
#endif /* TARGET_OS_IPHONE */
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import "DOUEAGLView.h"
typedef NS_ENUM(NSUInteger, DOUAudioVisualizerInterpolationType) {
DOUAudioVisualizerLinearInterpolation,
DOUAudioVisualizerSmoothInterpolation
};
@interface DOUAudioVisualizer : DOUEAGLView
@property (nonatomic, assign) NSUInteger stepCount;
@property (nonatomic, assign) DOUAudioVisualizerInterpolationType interpolationType;
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import "DOUAudioVisualizer.h"
#import "DOUAudioStreamer.h"
#include <Accelerate/Accelerate.h>
#define kBarHeight 6.0
#define kBarHorizontalPadding 2.0
#define kBarVerticalPadding 1.0
@interface DOUAudioVisualizer () {
@private
struct {
float current[kDOUAudioAnalyzerLevelCount];
float last[kDOUAudioAnalyzerLevelCount];
float pacing[kDOUAudioAnalyzerLevelCount];
} _levels;
float _coefficient;
NSUInteger _step;
NSUInteger _stepCount;
DOUAudioVisualizerInterpolationType _interpolationType;
struct {
CGFloat width;
CGFloat height;
CGFloat horizontalPadding;
CGFloat verticalPadding;
NSUInteger horizontalCount;
NSUInteger verticalCount;
} _bar;
GLuint _vbo;
GLuint _ibo;
}
@end
@implementation DOUAudioVisualizer
@synthesize stepCount = _stepCount;
@synthesize interpolationType = _interpolationType;
#pragma mark - Shared Analyzer
+ (void)_applicationDidEnterBackgroundNotification:(NSNotification *)notification
{
[[self _sharedAnalyzer] setEnabled:NO];
}
+ (void)_applicationWillEnterForegroundNotification:(NSNotification *)notification
{
[[self _sharedAnalyzer] setEnabled:YES];
}
+ (void)_setupNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationDidEnterBackgroundNotification:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationWillEnterForegroundNotification:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
+ (DOUAudioAnalyzer *)_sharedAnalyzer
{
static DOUAudioAnalyzer *sharedAnalyzer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAnalyzer = [DOUAudioAnalyzer frequencyAnalyzer];
[sharedAnalyzer setEnabled:YES];
[DOUAudioStreamer setAnalyzers:@[sharedAnalyzer]];
[self performSelector:@selector(_setupNotifications)
withObject:nil
afterDelay:0.0];
});
return sharedAnalyzer;
}
#pragma mark - Miscellaneous
- (void)_applicationDidEnterBackgroundNotification:(NSNotification *)notification
{
[self setPaused:YES];
}
- (void)_applicationWillEnterForegroundNotification:(NSNotification *)notification
{
[self setPaused:NO];
}
- (void)_setupNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationDidEnterBackgroundNotification:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationWillEnterForegroundNotification:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)_setupVisualizer
{
_coefficient = 0.0f;
_step = 0;
_stepCount = 6;
_interpolationType = DOUAudioVisualizerLinearInterpolation;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _setupNotifications];
[self _setupVisualizer];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self _setupNotifications];
[self _setupVisualizer];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Animation
- (void)_updateStepAndLevels
{
#define COEFFICIENT ((float)_step / _stepCount)
#define INTERPOLATED_COEFFICIENT_LINEAR (COEFFICIENT)
#define INTERPOLATED_COEFFICIENT_SMOOTH (sinf(COEFFICIENT * M_PI - M_PI_2) * 0.5f + 0.5f)
switch (_interpolationType) {
default:
case DOUAudioVisualizerLinearInterpolation:
_coefficient = INTERPOLATED_COEFFICIENT_LINEAR;
break;
case DOUAudioVisualizerSmoothInterpolation:
_coefficient = INTERPOLATED_COEFFICIENT_SMOOTH;
break;
}
if (_coefficient >= 1.0f) {
_coefficient = 0.0f;
}
#undef COEFFICIENT
#undef INTERPOLATED_COEFFICIENT_LINEAR
#undef INTERPOLATED_COEFFICIENT_SMOOTH
if (++_step > _stepCount) {
_step = 0;
memcpy(_levels.last, _levels.current, sizeof(float) * kDOUAudioAnalyzerLevelCount);
[[[self class] _sharedAnalyzer] copyLevels:_levels.current];
}
}
- (void)_updatePacingLevels
{
vDSP_vintb(_levels.last, 1,
_levels.current, 1,
&_coefficient,
_levels.pacing, 1,
kDOUAudioAnalyzerLevelCount);
}
#pragma mark - Pre-calculation
- (void)_updateBarGeometries
{
CGFloat width = CGRectGetWidth([self bounds]);
CGFloat height = CGRectGetHeight([self bounds]);
_bar.width = width / kDOUAudioAnalyzerLevelCount;
_bar.height = kBarHeight;
_bar.horizontalPadding = kBarHorizontalPadding;
_bar.verticalPadding = kBarVerticalPadding;
_bar.horizontalCount = kDOUAudioAnalyzerLevelCount;
_bar.verticalCount = (NSUInteger)lrint(floor(height / kBarHeight));
}
- (void)_updateVBO
{
[self _updateBarGeometries];
NSUInteger verticesCount = _bar.verticalCount * 4 * 2;
GLfloat *vertices = (GLfloat *)malloc(sizeof(GLfloat) * verticesCount);
NSUInteger indicesCount = _bar.verticalCount * 4;
GLuint *indices = (GLuint *)malloc(sizeof(GLuint) * indicesCount);
for (NSUInteger i = 0; i < _bar.verticalCount; ++i) {
CGRect rect;
rect.origin.x = _bar.horizontalPadding;
rect.origin.y = _bar.verticalPadding + _bar.height * i;
rect.size.width = _bar.width - 2.0 * _bar.horizontalPadding;
rect.size.height = _bar.height - 2.0 * _bar.verticalPadding;
if (i & 1) {
vertices[i * 4 * 2 + 0 * 2 + 0] = CGRectGetMaxX(rect);
vertices[i * 4 * 2 + 0 * 2 + 1] = CGRectGetMinY(rect);
vertices[i * 4 * 2 + 1 * 2 + 0] = CGRectGetMaxX(rect);
vertices[i * 4 * 2 + 1 * 2 + 1] = CGRectGetMaxY(rect);
vertices[i * 4 * 2 + 2 * 2 + 0] = CGRectGetMinX(rect);
vertices[i * 4 * 2 + 2 * 2 + 1] = CGRectGetMinY(rect);
vertices[i * 4 * 2 + 3 * 2 + 0] = CGRectGetMinX(rect);
vertices[i * 4 * 2 + 3 * 2 + 1] = CGRectGetMaxY(rect);
}
else {
vertices[i * 4 * 2 + 0 * 2 + 0] = CGRectGetMinX(rect);
vertices[i * 4 * 2 + 0 * 2 + 1] = CGRectGetMinY(rect);
vertices[i * 4 * 2 + 1 * 2 + 0] = CGRectGetMinX(rect);
vertices[i * 4 * 2 + 1 * 2 + 1] = CGRectGetMaxY(rect);
vertices[i * 4 * 2 + 2 * 2 + 0] = CGRectGetMaxX(rect);
vertices[i * 4 * 2 + 2 * 2 + 1] = CGRectGetMinY(rect);
vertices[i * 4 * 2 + 3 * 2 + 0] = CGRectGetMaxX(rect);
vertices[i * 4 * 2 + 3 * 2 + 1] = CGRectGetMaxY(rect);
}
indices[i * 4 + 0] = (GLuint)i * 4 + 0;
indices[i * 4 + 1] = (GLuint)i * 4 + 1;
indices[i * 4 + 2] = (GLuint)i * 4 + 2;
indices[i * 4 + 3] = (GLuint)i * 4 + 3;
}
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(sizeof(GLfloat) * verticesCount), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(GLuint) * indicesCount), indices, GL_STATIC_DRAW);
free(vertices);
free(indices);
}
#pragma mark - Renderer
- (void)prepare
{
glGenBuffers(1, &_vbo);
glGenBuffers(1, &_ibo);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glColor4f(0.784f, 0.867f, 0.839f, 1.0f);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
- (void)cleanup
{
glDeleteBuffers(1, &_vbo);
glDeleteBuffers(1, &_ibo);
}
- (void)reshape
{
CGFloat width = CGRectGetWidth([self bounds]);
CGFloat height = CGRectGetHeight([self bounds]);
CGFloat scale = [[UIScreen mainScreen] scale];
glViewport(0, 0, (GLsizei)lrint(width * scale), (GLsizei)lrint(height * scale));
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(0.0f, width, 0.0f, height, -100.0f, 100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
[self _updateVBO];
}
- (void)render
{
glClear(GL_COLOR_BUFFER_BIT);
[self _updateStepAndLevels];
[self _updatePacingLevels];
glEnableClientState(GL_VERTEX_ARRAY);
for (NSUInteger i = 0; i < _bar.horizontalCount; ++i) {
NSUInteger verticalCount = (NSUInteger)lroundf(_levels.pacing[i] * _bar.verticalCount);
glPushMatrix();
glTranslatef(_bar.width * i, 0.0f, 0.0f);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glVertexPointer(2, GL_FLOAT, 0, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
glDrawElements(GL_TRIANGLE_STRIP, (GLsizei)verticalCount * 4, GL_UNSIGNED_INT_OES, NULL);
glPopMatrix();
}
glDisableClientState(GL_VERTEX_ARRAY);
}
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
@interface DOUEAGLView : UIView
@property (nonatomic, getter=isPaused) BOOL paused;
@property (nonatomic, assign) NSInteger frameInterval;
- (void)prepare;
- (void)cleanup;
- (void)reshape;
- (void)render;
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import "DOUEAGLView.h"
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
@interface DOUEAGLView () {
@private
EAGLContext *_context;
CAEAGLLayer *_eaglLayer;
CADisplayLink *_displayLink;
NSThread *_displayLinkThread;
GLuint _framebuffer;
GLuint _renderbufferColor;
}
@end
@implementation DOUEAGLView
@dynamic paused;
@dynamic frameInterval;
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self _initialize];
}
return self;
}
- (void)dealloc
{
[self _finalize];
}
- (void)_initialize
{
[self setOpaque:NO];
[self setBackgroundColor:[UIColor clearColor]];
[self _setupEAGLContextAndLayer];
[self _setupFBO];
[self _setupDisplayLink];
}
- (void)_finalize
{
[_displayLink invalidate];
[EAGLContext setCurrentContext:_context];
[self cleanup];
glDeleteFramebuffersOES(1, &_framebuffer);
glDeleteRenderbuffersOES(1, &_renderbufferColor);
[EAGLContext setCurrentContext:nil];
}
- (void)_setupEAGLContextAndLayer
{
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
[EAGLContext setCurrentContext:_context];
[self prepare];
_eaglLayer = (CAEAGLLayer *)[self layer];
[_eaglLayer setOpaque:NO];
[_eaglLayer setContentsScale:[[UIScreen mainScreen] scale]];
[_eaglLayer setDrawableProperties:@{
kEAGLDrawablePropertyRetainedBacking: @NO,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
}];
}
- (void)_setupFBO
{
glGenFramebuffersOES(1, &_framebuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, _framebuffer);
glGenRenderbuffersOES(1, &_renderbufferColor);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, _renderbufferColor);
[_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:_eaglLayer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _renderbufferColor);
if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
abort();
}
}
- (void)layoutSubviews
{
[EAGLContext setCurrentContext:_context];
glBindRenderbufferOES(GL_RENDERBUFFER_OES, _renderbufferColor);
[_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:_eaglLayer];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, _framebuffer);
if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
abort();
}
[self reshape];
}
- (void)_setupDisplayLink
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(_displayLinkCallback:)];
[_displayLink setPaused:NO];
[_displayLink setFrameInterval:1];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
}
- (void)_displayLinkCallback:(CADisplayLink *)displayLink
{
@autoreleasepool {
[EAGLContext setCurrentContext:_context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, _framebuffer);
[self render];
glBindRenderbufferOES(GL_RENDERBUFFER_OES, _renderbufferColor);
[_context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
}
- (void)prepare
{
}
- (void)cleanup
{
}
- (void)reshape
{
}
- (void)render
{
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(isPaused) ||
aSelector == @selector(setPaused:) ||
aSelector == @selector(frameInterval) ||
aSelector == @selector(setFrameInterval:)) {
return _displayLink;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import <Foundation/Foundation.h>
typedef void (^DOUMPMediaLibraryAssetLoaderCompletedBlock)(void);
@interface DOUMPMediaLibraryAssetLoader : NSObject
+ (instancetype)loaderWithURL:(NSURL *)url;
- (instancetype)initWithURL:(NSURL *)url;
@property (nonatomic, strong, readonly) NSURL *assetURL;
@property (nonatomic, strong, readonly) NSString *cachedPath;
@property (nonatomic, strong, readonly) NSString *mimeType;
@property (nonatomic, strong, readonly) NSString *fileExtension;
@property (nonatomic, assign, readonly, getter=isFailed) BOOL failed;
@property (copy) DOUMPMediaLibraryAssetLoaderCompletedBlock completedBlock;
- (void)start;
- (void)cancel;
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#if TARGET_OS_IPHONE
#import "DOUMPMediaLibraryAssetLoader.h"
#import <AVFoundation/AVFoundation.h>
#include <CommonCrypto/CommonDigest.h>
@interface DOUMPMediaLibraryAssetLoader () {
@private
NSString *_cachedPath;
AVAssetExportSession *_exportSession;
}
@end
@implementation DOUMPMediaLibraryAssetLoader
+ (instancetype)loaderWithURL:(NSURL *)url
{
return [[[self class] alloc] initWithURL:url];
}
- (instancetype)initWithURL:(NSURL *)url
{
self = [super init];
if (self) {
_assetURL = url;
}
return self;
}
- (void)start
{
if (_exportSession != nil) {
return;
}
AVURLAsset *asset = [AVURLAsset assetWithURL:_assetURL];
if (asset == nil) {
[self _reportFailure];
return;
}
_exportSession = [AVAssetExportSession exportSessionWithAsset:asset
presetName:AVAssetExportPresetPassthrough];
if (_exportSession == nil) {
[self _reportFailure];
return;
}
[_exportSession setOutputFileType:AVFileTypeCoreAudioFormat];
[_exportSession setOutputURL:[NSURL fileURLWithPath:[self cachedPath]]];
__weak typeof(self) weakSelf = self;
[_exportSession exportAsynchronouslyWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf _exportSessionDidComplete];
}];
}
- (void)cancel
{
if (_exportSession == nil) {
return;
}
[_exportSession cancelExport];
_exportSession = nil;
}
- (void)_exportSessionDidComplete
{
if ([_exportSession status] != AVAssetExportSessionStatusCompleted ||
[_exportSession error] != nil) {
[self _reportFailure];
return;
}
[self _invokeCompletedBlock];
}
- (void)_invokeCompletedBlock
{
@synchronized(self) {
if (_completedBlock != NULL) {
_completedBlock();
}
}
}
- (void)_reportFailure
{
_failed = YES;
[self _invokeCompletedBlock];
}
- (NSString *)cachedPath
{
if (_cachedPath == nil) {
NSString *filename = [NSString stringWithFormat:@"douas-mla-%@.%@", [[self class] _sha256ForURL:_assetURL], [self fileExtension]];
_cachedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:_cachedPath]) {
[[NSFileManager defaultManager] removeItemAtPath:_cachedPath error:NULL];
}
}
return _cachedPath;
}
- (NSString *)mimeType
{
return AVFileTypeCoreAudioFormat;
}
- (NSString *)fileExtension
{
return @"caf";
}
+ (NSString *)_sha256ForURL:(NSURL *)url
{
NSString *string = [url absoluteString];
unsigned char hash[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([string UTF8String], (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", hash[i]];
}
return result;
}
@end
#endif /* TARGET_OS_IPHONE */
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
typedef void (^DOUSimpleHTTPRequestCompletedBlock)(void);
typedef void (^DOUSimpleHTTPRequestProgressBlock)(double downloadProgress);
typedef void (^DOUSimpleHTTPRequestDidReceiveResponseBlock)(void);
typedef void (^DOUSimpleHTTPRequestDidReceiveDataBlock)(NSData *data);
@interface DOUSimpleHTTPRequest : NSObject
+ (instancetype)requestWithURL:(NSURL *)url;
- (instancetype)initWithURL:(NSURL *)url;
+ (NSTimeInterval)defaultTimeoutInterval;
+ (NSString *)defaultUserAgent;
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
@property (nonatomic, strong) NSString *userAgent;
@property (nonatomic, strong) NSString *host;
@property (nonatomic, readonly) NSData *responseData;
@property (nonatomic, readonly) NSString *responseString;
@property (nonatomic, readonly) NSDictionary *responseHeaders;
@property (nonatomic, readonly) NSUInteger responseContentLength;
@property (nonatomic, readonly) NSInteger statusCode;
@property (nonatomic, readonly) NSString *statusMessage;
@property (nonatomic, readonly) NSUInteger downloadSpeed;
@property (nonatomic, readonly, getter=isFailed) BOOL failed;
@property (copy) DOUSimpleHTTPRequestCompletedBlock completedBlock;
@property (copy) DOUSimpleHTTPRequestProgressBlock progressBlock;
@property (copy) DOUSimpleHTTPRequestDidReceiveResponseBlock didReceiveResponseBlock;
@property (copy) DOUSimpleHTTPRequestDidReceiveDataBlock didReceiveDataBlock;
- (void)start;
- (void)cancel;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "DOUSimpleHTTPRequest.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <pthread.h>
static struct {
pthread_t thread;
pthread_mutex_t mutex;
pthread_cond_t cond;
CFRunLoopRef runloop;
} controller;
static void *controller_main(void *info)
{
pthread_setname_np("com.douban.simple-http-request.controller");
pthread_mutex_lock(&controller.mutex);
controller.runloop = CFRunLoopGetCurrent();
pthread_mutex_unlock(&controller.mutex);
pthread_cond_signal(&controller.cond);
CFRunLoopSourceContext context;
bzero(&context, sizeof(context));
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(controller.runloop, source, kCFRunLoopDefaultMode);
CFRunLoopRun();
CFRunLoopRemoveSource(controller.runloop, source, kCFRunLoopDefaultMode);
CFRelease(source);
pthread_mutex_destroy(&controller.mutex);
pthread_cond_destroy(&controller.cond);
return NULL;
}
static CFRunLoopRef controller_get_runloop()
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pthread_mutex_init(&controller.mutex, NULL);
pthread_cond_init(&controller.cond, NULL);
controller.runloop = NULL;
pthread_create(&controller.thread, NULL, controller_main, NULL);
pthread_mutex_lock(&controller.mutex);
if (controller.runloop == NULL) {
pthread_cond_wait(&controller.cond, &controller.mutex);
}
pthread_mutex_unlock(&controller.mutex);
});
return controller.runloop;
}
@interface DOUSimpleHTTPRequest () {
@private
DOUSimpleHTTPRequestCompletedBlock _completedBlock;
DOUSimpleHTTPRequestProgressBlock _progressBlock;
DOUSimpleHTTPRequestDidReceiveResponseBlock _didReceiveResponseBlock;
DOUSimpleHTTPRequestDidReceiveDataBlock _didReceiveDataBlock;
NSString *_userAgent;
NSTimeInterval _timeoutInterval;
CFHTTPMessageRef _message;
CFReadStreamRef _responseStream;
NSDictionary *_responseHeaders;
NSMutableData *_responseData;
NSString *_responseString;
NSInteger _statusCode;
NSString *_statusMessage;
BOOL _failed;
CFAbsoluteTime _startedTime;
NSUInteger _downloadSpeed;
NSUInteger _responseContentLength;
NSUInteger _receivedLength;
}
@end
@implementation DOUSimpleHTTPRequest
@synthesize timeoutInterval = _timeoutInterval;
@synthesize userAgent = _userAgent;
@synthesize responseData = _responseData;
@synthesize responseHeaders = _responseHeaders;
@synthesize responseContentLength = _responseContentLength;
@synthesize statusCode = _statusCode;
@synthesize statusMessage = _statusMessage;
@synthesize downloadSpeed = _downloadSpeed;
@synthesize failed = _failed;
@synthesize completedBlock = _completedBlock;
@synthesize progressBlock = _progressBlock;
@synthesize didReceiveResponseBlock = _didReceiveResponseBlock;
@synthesize didReceiveDataBlock = _didReceiveDataBlock;
+ (instancetype)requestWithURL:(NSURL *)url
{
if (url == nil) {
return nil;
}
return [[[self class] alloc] initWithURL:url];
}
- (instancetype)initWithURL:(NSURL *)url
{
self = [super init];
if (self) {
_userAgent = [[self class] defaultUserAgent];
_timeoutInterval = [[self class] defaultTimeoutInterval];
_message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
}
return self;
}
- (void)dealloc
{
if (_responseStream != NULL) {
[self _closeResponseStream];
CFRelease(_responseStream);
}
CFRelease(_message);
}
+ (NSTimeInterval)defaultTimeoutInterval
{
return 20.0;
}
+ (NSString *)defaultUserAgent
{
static NSString *defaultUserAgent = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *appName = [infoDict objectForKey:@"CFBundleName"];
NSString *shortVersion = [infoDict objectForKey:@"CFBundleShortVersionString"];
NSString *bundleVersion = [infoDict objectForKey:@"CFBundleVersion"];
NSString *deviceName = nil;
NSString *systemName = nil;
NSString *systemVersion = nil;
#if TARGET_OS_IPHONE
UIDevice *device = [UIDevice currentDevice];
deviceName = [device model];
systemName = [device systemName];
systemVersion = [device systemVersion];
#else /* TARGET_OS_IPHONE */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
SInt32 versionMajor, versionMinor, versionBugFix;
Gestalt(gestaltSystemVersionMajor, &versionMajor);
Gestalt(gestaltSystemVersionMinor, &versionMinor);
Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
#pragma clang diagnostic pop
int mib[2] = { CTL_HW, HW_MODEL };
size_t len = 0;
sysctl(mib, 2, NULL, &len, NULL, 0);
char *hw_model = malloc(len);
sysctl(mib, 2, hw_model, &len, NULL, 0);
deviceName = [NSString stringWithFormat:@"Macintosh %s", hw_model];
free(hw_model);
systemName = @"Mac OS X";
systemVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix];
#endif /* TARGET_OS_IPHONE */
NSString *locale = [[NSLocale currentLocale] localeIdentifier];
defaultUserAgent = [NSString stringWithFormat:@"%@ %@ build %@ (%@; %@ %@; %@)", appName, shortVersion, bundleVersion, deviceName, systemName, systemVersion, locale];
});
return defaultUserAgent;
}
- (void)_invokeCompletedBlock
{
@synchronized(self) {
if (_completedBlock != NULL) {
_completedBlock();
}
}
}
- (void)_invokeProgressBlockWithDownloadProgress:(double)downloadProgress
{
@synchronized(self) {
if (_progressBlock != NULL) {
_progressBlock(downloadProgress);
}
}
}
- (void)_invokeDidReceiveResponseBlock
{
@synchronized(self) {
if (_didReceiveResponseBlock != NULL) {
_didReceiveResponseBlock();
}
}
}
- (void)_invokeDidReceiveDataBlockWithData:(NSData *)data
{
@synchronized(self) {
if (_didReceiveDataBlock != NULL) {
_didReceiveDataBlock(data);
}
}
}
- (void)_checkResponseContentLength
{
if (_responseHeaders == nil) {
return;
}
NSString *string = [_responseHeaders objectForKey:@"Content-Length"];
if (string == nil) {
return;
}
_responseContentLength = (NSUInteger)[string integerValue];
}
- (void)_readResponseHeaders
{
if (_responseHeaders != nil) {
return;
}
CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty(_responseStream, kCFStreamPropertyHTTPResponseHeader);
if (message == NULL) {
return;
}
if (!CFHTTPMessageIsHeaderComplete(message)) {
CFRelease(message);
return;
}
_responseHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(message));
_statusCode = CFHTTPMessageGetResponseStatusCode(message);
_statusMessage = CFBridgingRelease(CFHTTPMessageCopyResponseStatusLine(message));
CFRelease(message);
[self _checkResponseContentLength];
[self _invokeDidReceiveResponseBlock];
}
- (void)_updateProgress
{
double downloadProgress;
if (_responseContentLength == 0) {
if (_responseHeaders != nil) {
downloadProgress = 1.0;
}
else {
downloadProgress = 0.0;
}
}
else {
downloadProgress = (double)_receivedLength / _responseContentLength;
}
[self _invokeProgressBlockWithDownloadProgress:downloadProgress];
}
- (void)_updateDownloadSpeed
{
_downloadSpeed = _receivedLength / (CFAbsoluteTimeGetCurrent() - _startedTime);
}
- (void)_closeResponseStream
{
CFReadStreamClose(_responseStream);
CFReadStreamUnscheduleFromRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode);
CFReadStreamSetClient(_responseStream, kCFStreamEventNone, NULL, NULL);
}
- (void)_responseStreamHasBytesAvailable
{
[self _readResponseHeaders];
if (!CFReadStreamHasBytesAvailable(_responseStream)) {
return;
}
CFIndex bufferSize;
if (_responseContentLength > 262144) {
bufferSize = 262144;
}
else if (_responseContentLength > 65536) {
bufferSize = 65536;
}
else {
bufferSize = 16384;
}
UInt8 buffer[bufferSize];
CFIndex bytesRead = CFReadStreamRead(_responseStream, buffer, bufferSize);
if (bytesRead < 0) {
[self _responseStreamErrorOccurred];
return;
}
if (bytesRead > 0) {
NSData *data = [NSData dataWithBytesNoCopy:buffer length:(NSUInteger)bytesRead freeWhenDone:NO];
@synchronized(self) {
if (_didReceiveDataBlock == NULL) {
if (_responseData == nil) {
_responseData = [NSMutableData data];
}
[_responseData appendData:data];
}
else {
[self _invokeDidReceiveDataBlockWithData:data];
}
}
_receivedLength += (unsigned long)bytesRead;
[self _updateProgress];
[self _updateDownloadSpeed];
}
}
- (void)_responseStrameEndEncountered
{
[self _readResponseHeaders];
[self _invokeProgressBlockWithDownloadProgress:1.0];
[self _invokeCompletedBlock];
}
- (void)_responseStreamErrorOccurred
{
[self _readResponseHeaders];
_failed = YES;
[self _closeResponseStream];
[self _invokeCompletedBlock];
}
static void response_stream_client_callback(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
{
@autoreleasepool {
__unsafe_unretained DOUSimpleHTTPRequest *request = (__bridge DOUSimpleHTTPRequest *)clientCallBackInfo;
@synchronized(request) {
switch (type) {
case kCFStreamEventHasBytesAvailable:
[request _responseStreamHasBytesAvailable];
break;
case kCFStreamEventEndEncountered:
[request _responseStrameEndEncountered];
break;
case kCFStreamEventErrorOccurred:
[request _responseStreamErrorOccurred];
break;
default:
break;
}
}
}
}
- (void)start
{
if (_responseStream != NULL) {
return;
}
CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("User-Agent"), (__bridge CFStringRef)_userAgent);
if (_host != nil) {
CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("Host"), (__bridge CFStringRef)_host);
}
_responseStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, _message);
CFReadStreamSetProperty(_responseStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyReadTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]);
CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyWriteTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]);
CFStreamClientContext context;
bzero(&context, sizeof(context));
context.info = (__bridge void *)self;
CFReadStreamSetClient(_responseStream, kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, response_stream_client_callback, &context);
CFReadStreamScheduleWithRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode);
CFReadStreamOpen(_responseStream);
_startedTime = CFAbsoluteTimeGetCurrent();
_downloadSpeed = 0;
}
- (void)cancel
{
if (_responseStream == NULL || _failed) {
return;
}
__block CFTypeRef __request = CFBridgingRetain(self);
CFRunLoopPerformBlock(controller_get_runloop(), kCFRunLoopDefaultMode, ^{
@autoreleasepool {
[(__bridge DOUSimpleHTTPRequest *)__request _closeResponseStream];
CFBridgingRelease(__request);
}
});
}
- (NSString *)responseString
{
if (_responseData == nil) {
return nil;
}
if (_responseString == nil) {
_responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
}
return _responseString;
}
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import <Foundation/Foundation.h>
@interface NSData (DOUAudioMappedFile)
+ (instancetype)dou_dataWithMappedContentsOfFile:(NSString *)path;
+ (instancetype)dou_dataWithMappedContentsOfURL:(NSURL *)url;
+ (instancetype)dou_modifiableDataWithMappedContentsOfFile:(NSString *)path;
+ (instancetype)dou_modifiableDataWithMappedContentsOfURL:(NSURL *)url;
- (void)dou_synchronizeMappedFile;
@end
/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */
/*
* DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac:
*
* https://github.com/douban/DOUAudioStreamer
*
* Copyright 2013-2016 Douban Inc. All rights reserved.
*
* Use and distribution licensed under the BSD license. See
* the LICENSE file for full text.
*
* Authors:
* Chongyu Zhu <i@lembacon.com>
*
*/
#import "NSData+DOUAudioMappedFile.h"
#include <sys/types.h>
#include <sys/mman.h>
static NSMutableDictionary *get_size_map()
{
static NSMutableDictionary *map = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = [[NSMutableDictionary alloc] init];
});
return map;
}
static void mmap_deallocate(void *ptr, void *info)
{
NSNumber *key = [NSNumber numberWithUnsignedLongLong:(uintptr_t)ptr];
NSNumber *fileSize = nil;
NSMutableDictionary *sizeMap = get_size_map();
@synchronized(sizeMap) {
fileSize = [sizeMap objectForKey:key];
[sizeMap removeObjectForKey:key];
}
size_t size = (size_t)[fileSize unsignedLongLongValue];
munmap(ptr, size);
}
static CFAllocatorRef get_mmap_deallocator()
{
static CFAllocatorRef deallocator = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFAllocatorContext context;
bzero(&context, sizeof(context));
context.deallocate = mmap_deallocate;
deallocator = CFAllocatorCreate(kCFAllocatorDefault, &context);
});
return deallocator;
}
@implementation NSData (DOUAudioMappedFile)
+ (instancetype)dou_dataWithMappedContentsOfFile:(NSString *)path
{
return [[self class] _dou_dataWithMappedContentsOfFile:path modifiable:NO];
}
+ (instancetype)dou_dataWithMappedContentsOfURL:(NSURL *)url
{
return [[self class] dou_dataWithMappedContentsOfFile:[url path]];
}
+ (instancetype)dou_modifiableDataWithMappedContentsOfFile:(NSString *)path
{
return [[self class] _dou_dataWithMappedContentsOfFile:path modifiable:YES];
}
+ (instancetype)dou_modifiableDataWithMappedContentsOfURL:(NSURL *)url
{
return [[self class] dou_modifiableDataWithMappedContentsOfFile:[url path]];
}
+ (instancetype)_dou_dataWithMappedContentsOfFile:(NSString *)path modifiable:(BOOL)modifiable
{
NSFileHandle *fileHandle = nil;
if (modifiable) {
fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:path];
}
else {
fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
}
if (fileHandle == nil) {
return nil;
}
int fd = [fileHandle fileDescriptor];
if (fd < 0) {
return nil;
}
off_t size = lseek(fd, 0, SEEK_END);
if (size < 0) {
return nil;
}
int protection = PROT_READ;
if (modifiable) {
protection |= PROT_WRITE;
}
void *address = mmap(NULL, (size_t)size, protection, MAP_FILE | MAP_SHARED, fd, 0);
if (address == MAP_FAILED) {
return nil;
}
NSMutableDictionary *sizeMap = get_size_map();
@synchronized(sizeMap) {
[sizeMap setObject:[NSNumber numberWithUnsignedLongLong:(unsigned long long)size]
forKey:[NSNumber numberWithUnsignedLongLong:(uintptr_t)address]];
}
return CFBridgingRelease(CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)address, (CFIndex)size, get_mmap_deallocator()));
}
- (void)dou_synchronizeMappedFile
{
NSNumber *key = [NSNumber numberWithUnsignedLongLong:(uintptr_t)[self bytes]];
NSNumber *fileSize = nil;
NSMutableDictionary *sizeMap = get_size_map();
@synchronized(sizeMap) {
fileSize = [sizeMap objectForKey:key];
}
if (fileSize == nil) {
return;
}
size_t size = (size_t)[fileSize unsignedLongLongValue];
msync((void *)[self bytes], size, MS_SYNC | MS_INVALIDATE);
}
@end
......@@ -19,6 +19,7 @@ PODS:
- DKNightVersion/Core
- DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core
- DOUAudioStreamer (0.2.16)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MJRefresh (3.7.5)
......@@ -27,6 +28,7 @@ PODS:
DEPENDENCIES:
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MJRefresh (~> 3.7.5)
......@@ -36,6 +38,7 @@ SPEC REPOS:
trunk:
- AFNetworking
- DKNightVersion
- DOUAudioStreamer
- lottie-ios
- Masonry
- MJRefresh
......@@ -44,11 +47,12 @@ SPEC REPOS:
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
PODFILE CHECKSUM: da4f3494cbd800091ea38764f7f43754ca9369ea
PODFILE CHECKSUM: e7bb0ee0a6fff85de99e8ba732a1592a8a5ab9c4
COCOAPODS: 1.11.3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.2.16</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_DOUAudioStreamer : NSObject
@end
@implementation PodsDummy_DOUAudioStreamer
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "DOUAudioAnalyzer+Default.h"
#import "DOUAudioAnalyzer.h"
#import "DOUAudioAnalyzer_Private.h"
#import "DOUAudioBase.h"
#import "DOUAudioDecoder.h"
#import "DOUAudioEventLoop.h"
#import "DOUAudioFile.h"
#import "DOUAudioFilePreprocessor.h"
#import "DOUAudioFileProvider.h"
#import "DOUAudioFrequencyAnalyzer.h"
#import "DOUAudioLPCM.h"
#import "DOUAudioPlaybackItem.h"
#import "DOUAudioRenderer.h"
#import "DOUAudioSpatialAnalyzer.h"
#import "DOUAudioStreamer+Options.h"
#import "DOUAudioStreamer.h"
#import "DOUAudioStreamer_Private.h"
#import "DOUAudioVisualizer.h"
#import "DOUEAGLView.h"
#import "DOUMPMediaLibraryAssetLoader.h"
#import "DOUSimpleHTTPRequest.h"
#import "NSData+DOUAudioMappedFile.h"
FOUNDATION_EXPORT double DOUAudioStreamerVersionNumber;
FOUNDATION_EXPORT const unsigned char DOUAudioStreamerVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "Accelerate" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/DOUAudioStreamer
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module DOUAudioStreamer {
umbrella header "DOUAudioStreamer-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "Accelerate" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/DOUAudioStreamer
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
......@@ -50,6 +50,40 @@ SOFTWARE.
## DOUAudioStreamer
Copyright (c) 2013-2016, Douban Inc. <http://www.douban.com/>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the Douban Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## MJRefresh
Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh)
......
......@@ -75,6 +75,46 @@ SOFTWARE.
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2013-2016, Douban Inc. &lt;http://www.douban.com/&gt;
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the Douban Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<key>License</key>
<string>BSD</string>
<key>Title</key>
<string>DOUAudioStreamer</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh)
Permission is hereby granted, free of charge, to any person obtaining a copy
......
${PODS_ROOT}/Target Support Files/Pods-DreamSleep/Pods-DreamSleep-frameworks.sh
${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
......
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
......
${PODS_ROOT}/Target Support Files/Pods-DreamSleep/Pods-DreamSleep-frameworks.sh
${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
......
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
......
${PODS_ROOT}/Target Support Files/Pods-DreamSleep/Pods-DreamSleep-frameworks.sh
${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
......
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
......
......@@ -178,6 +178,7 @@ code_sign_if_enabled() {
if [[ "$CONFIGURATION" == "Beta" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
......@@ -186,6 +187,7 @@ fi
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
......@@ -194,6 +196,7 @@ fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork" -framework "DKNightVersion" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "UIKit" -framework "YTKNetwork"
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "Lottie" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!