Commit f02ca2ec cgx

引入FreeStreamer,完成播放进度条、播放按钮、单节播放、单曲循环、顺序播放功能

1 个父辈 45be73fe
正在显示 76 个修改的文件 包含 3436 行增加20 行删除
......@@ -109,6 +109,7 @@
D09D0E96280D3FE9008DEDAB /* NSDate+Extras.m in Sources */ = {isa = PBXBuildFile; fileRef = D09D0E95280D3FE9008DEDAB /* NSDate+Extras.m */; };
D09D0E9A280D507F008DEDAB /* ProfileAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = D09D0E99280D507F008DEDAB /* ProfileAlertView.m */; };
D09D0E9D280D73B6008DEDAB /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = D09D0E9C280D73B6008DEDAB /* InviteController.m */; };
D0AE1E3528281B6F008CEF27 /* TimerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE1E3428281B6F008CEF27 /* TimerProxy.m */; };
D0AEFE79281781CF00230DC6 /* MyFeedModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AEFE78281781CF00230DC6 /* MyFeedModel.m */; };
D0AEFE7C2817D13400230DC6 /* UITableViewCell+CardRadius.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AEFE7A2817D13400230DC6 /* UITableViewCell+CardRadius.m */; };
D0AEFE812817DD1500230DC6 /* MyFeedCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AEFE7E2817DD1500230DC6 /* MyFeedCell.m */; };
......@@ -369,6 +370,8 @@
D09D0E99280D507F008DEDAB /* ProfileAlertView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProfileAlertView.m; sourceTree = "<group>"; };
D09D0E9B280D73B6008DEDAB /* InviteController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InviteController.h; sourceTree = "<group>"; };
D09D0E9C280D73B6008DEDAB /* InviteController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
D0AE1E3328281B6F008CEF27 /* TimerProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TimerProxy.h; sourceTree = "<group>"; };
D0AE1E3428281B6F008CEF27 /* TimerProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TimerProxy.m; sourceTree = "<group>"; };
D0AEFE77281781CF00230DC6 /* MyFeedModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyFeedModel.h; sourceTree = "<group>"; };
D0AEFE78281781CF00230DC6 /* MyFeedModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyFeedModel.m; sourceTree = "<group>"; };
D0AEFE7A2817D13400230DC6 /* UITableViewCell+CardRadius.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableViewCell+CardRadius.m"; sourceTree = "<group>"; };
......@@ -1063,6 +1066,8 @@
D0F9AC532826563400FD7A3B /* MusicPlayerController.m */,
D0F9AC5C282660CC00FD7A3B /* MusicPlayerView.h */,
D0F9AC5D282660CC00FD7A3B /* MusicPlayerView.m */,
D0AE1E3328281B6F008CEF27 /* TimerProxy.h */,
D0AE1E3428281B6F008CEF27 /* TimerProxy.m */,
);
path = Home;
sourceTree = "<group>";
......@@ -1543,6 +1548,7 @@
D0F808F52803D4E70097899F /* Track.m in Sources */,
D0E65FFC2807A654006562F2 /* NSSet+HYBUnicodeReadable.m in Sources */,
D0C50B3C27FD2EFD00DC68F0 /* PrivacyViewController.m in Sources */,
D0AE1E3528281B6F008CEF27 /* TimerProxy.m in Sources */,
D0FAC41D281B817D00D4B859 /* GKPhotoBrowser.m in Sources */,
D07A4B2A280EA6B600BA0EC0 /* UserInfoTableView.m in Sources */,
D0930F122801124E006B497A /* BaseNaviController.m in Sources */,
......
......@@ -110,9 +110,15 @@
} else {
// 跳转到播放页面
MusicPlayerController *playerVC = [[MusicPlayerController alloc] init];
playerVC.audioModel = model;
playerVC.subAudioArr = self.subAudioArr;
playerVC.currentIndex = indexPath.row;
// 筛选已经解锁的音频
NSMutableArray *tmpArr = [NSMutableArray array];
[self.subAudioArr enumerateObjectsUsingBlock:^(SubAudioModel * obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.is_lock == 0) {
[tmpArr addObject:obj];
}
}];
playerVC.playAudios = [tmpArr copy];
UINavigationController *naviVC = [[UINavigationController alloc] initWithRootViewController:playerVC];
[self presentViewController:naviVC animated:YES completion:nil];
}
......
......@@ -6,14 +6,14 @@
//
#import <UIKit/UIKit.h>
#import "SubAudioModel.h"
NS_ASSUME_NONNULL_BEGIN
/// 音频播放界面
@interface MusicPlayerController : UIViewController
@property (nonatomic, strong) SubAudioModel *audioModel;
@property (nonatomic, strong) NSArray *subAudioArr;
/// 解锁的音频列表
@property (nonatomic, strong) NSArray *playAudios;
/// 当前播放音频下标
@property (nonatomic, assign) NSInteger currentIndex;
@end
......
......@@ -7,9 +7,18 @@
#import "MusicPlayerController.h"
#import "MusicPlayerView.h"
#import <FSAudioController.h>
#import "TimerProxy.h"
#import "SubAudioModel.h"
@interface MusicPlayerController ()
@interface MusicPlayerController () <MusicPlayerViewDelegate>
@property (nonatomic, strong) MusicPlayerView *playerView;
/// AudioStream 播放器
@property (nonatomic, strong) FSAudioStream *audioStream;
/// 播放进度定时器
@property (nonatomic, strong) CADisplayLink *progressTimer;
/// 是否正在拖动滑块
@property (nonatomic, assign) BOOL isDraging;
@end
@implementation MusicPlayerController
......@@ -21,7 +30,142 @@
- (void)viewDidLoad {
[super viewDidLoad];
[self.playerView updatePlayerView:self.audioModel];
if (self.currentIndex >= self.playAudios.count) { return; }
SubAudioModel *currentAudioModel = self.playAudios[self.currentIndex];
[self.playerView updatePlayerView:currentAudioModel];
WS(weakSelf);
[self.audioStream playFromURL:[NSURL URLWithString:currentAudioModel.audio_url]];
[self.audioStream setOnStateChange:^(FSAudioStreamState state) {
weakSelf.playerView.isPlaying = state == kFsAudioStreamPlaying;
switch (state) {
case kFsAudioStreamRetrievingURL:
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
DSLog(@"retrieving URL -- 检索文件");
break;
case kFsAudioStreamStopped:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
DSLog(@"kFsAudioStreamStopped --- 停止播放了 ");
break;
case kFsAudioStreamBuffering: {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
DSLog(@"buffering --- 缓存中");
break;
}
case kFsAudioStreamPaused:
DSLog(@"暂停了");
break;
case kFsAudioStreamSeeking:
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
DSLog(@"kFsAudioStreamSeeking -- 快进 或者 快退");
break;
case kFsAudioStreamPlaying:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
DSLog(@"播放ing -- ");
break;
case kFsAudioStreamFailed:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
DSLog(@"音频文件加载失败");
break;
case kFsAudioStreamPlaybackCompleted:
[weakSelf audioStreamPlaybackCompleted];
break;
case kFsAudioStreamRetryingStarted:
DSLog(@"回放失败");
break;
case kFsAudioStreamRetryingSucceeded:
DSLog(@"重试成功");
break;
case kFsAudioStreamRetryingFailed:
DSLog(@"Failed to retry playback -- 重试失败");
break;
default:
break;
}
}];
self.audioStream.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
DSLog(@"音频加载失败:%ld", error);
};
TimerProxy *proxy = [TimerProxy proxyWithTarget:self];
self.progressTimer = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(updateProgress)];
[self.progressTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)dealloc {
[self.progressTimer invalidate];
self.progressTimer = nil;
}
- (void)updateProgress {
if (self.isDraging == YES) return;
FSStreamPosition cur = self.audioStream.currentTimePlayed;
FSStreamPosition end = self.audioStream.duration;
// 音频播放进度、音频当前播放时间、音频总时间
[self.playerView updateProgress:cur.position currentTime:[NSString stringWithFormat:@"%02i:%02i", cur.minute, cur.second] totalTime:[NSString stringWithFormat:@"%02i:%02i", end.minute, end.second]];
}
- (void)audioStreamPlaybackCompleted {
DSLog(@"kFsAudioStreamPlaybackCompleted -- 回放完成");
[self.progressTimer setPaused:YES];
if (self.playerView.mode == SoundPlayModeSingle) { // 单节播放
// 判断是否有下一首
if (self.currentIndex < self.playAudios.count - 1) {
self.currentIndex++;
SubAudioModel *audioModel = self.playAudios[self.currentIndex];
[self.playerView updatePlayerView:audioModel];
[self.audioStream playFromURL:[NSURL URLWithString:audioModel.audio_url]];
[self.progressTimer setPaused:NO];
} else {
FSStreamPosition pos = {0};
pos.position = 0;
[self.audioStream seekToPosition:pos];
FSStreamPosition end = self.audioStream.duration;
[self.playerView updateProgress:0 currentTime:@"00:00" totalTime:[NSString stringWithFormat:@"%02i:%02i", end.minute, end.second]];
}
} else if (self.playerView.mode == SoundPlayModeCycle) { // 单曲循环
[self.audioStream play];
[self.progressTimer setPaused:NO];
} else if (self.playerView.mode == SoundPlayModeOrder) { // 顺序播放
// 获取下一首需要播放的音频
if (self.currentIndex + 1 >= self.playAudios.count) {
self.currentIndex = 0;
} else {
self.currentIndex++;
}
SubAudioModel *audioModel = self.playAudios[self.currentIndex];
[self.playerView updatePlayerView:audioModel];
[self.audioStream playFromURL:[NSURL URLWithString:audioModel.audio_url]];
[self.progressTimer setPaused:NO];
}
}
#pragma mark - MusicPlayerViewDelegate
- (void)didSliderTouchBegan:(float)value {
self.isDraging = YES;
}
- (void)didSliderTouchEnded:(float)value {
self.isDraging = NO;
// 避免用户拖动进度条出现极值导致音频文件加载失败问题
if (value == 0) value = 0.001;
if (value == 1) value = 0.999;
FSStreamPosition pos = {0};
pos.position = value;
[self.audioStream seekToPosition:pos];
}
- (void)didSliderValueChange:(float)value {
self.isDraging = YES;
}
- (void)didClickPlayBtn {
// 播放和暂停是同一个方法
[self.audioStream pause];
}
#pragma mark - 隐藏导航栏
......@@ -38,8 +182,18 @@
- (MusicPlayerView *)playerView {
if (!_playerView) {
_playerView = [MusicPlayerView new];
_playerView.delegate = self;
}
return _playerView;
}
- (FSAudioStream *)audioStream {
if (_audioStream == nil) {
_audioStream = [[FSAudioStream alloc] init];
_audioStream.strictContentTypeChecking = NO;
_audioStream.defaultContentType = @"audio/mpeg";
}
return _audioStream;
}
@end
......@@ -16,11 +16,35 @@ typedef NS_ENUM(NSInteger, SoundPlayMode) {
NS_ASSUME_NONNULL_BEGIN
@protocol MusicPlayerViewDelegate <NSObject>
- (void)didSliderTouchBegan:(float)value;
- (void)didSliderTouchEnded:(float)value;
- (void)didSliderValueChange:(float)value;
/// 点击播放按钮事件
- (void)didClickPlayBtn;
@end
/// 音频播放页面
@interface MusicPlayerView : UIView
@property (nonatomic, weak) id<MusicPlayerViewDelegate> delegate;
/// 播放状态
@property (nonatomic, assign) BOOL isPlaying;
/// 音频播放模式
@property (nonatomic, assign) SoundPlayMode mode;
/// 更新播放界面标题和图片
/// @param model model
- (void)updatePlayerView:(SubAudioModel *)model;
/// 更新进度条
/// @param progress progress
/// @param currentTime 当前时间
/// @param totalTime 总时间
- (void)updateProgress:(float)progress currentTime:(NSString *)currentTime totalTime:(NSString *)totalTime;
@end
NS_ASSUME_NONNULL_END
......@@ -122,6 +122,12 @@
self.audioNameLab.text = model.audio_name;
}
- (void)updateProgress:(float)progress currentTime:(NSString *)currentTime totalTime:(NSString *)totalTime {
self.progressV.value = progress;
self.proLeftLb.text = currentTime;
self.proRightLb.text = totalTime;
}
- (void)circelBtnClick:(UIButton *)sender {
SoundPlayMode mode = sender.tag;
NSString *title = sender.titleLabel.text;
......@@ -148,15 +154,57 @@
[sender dk_setImage:DKImagePickerWithNames(normalImgName, dkImgName, normalImgName) forState:UIControlStateNormal];
}
#pragma mark - 滑块事件
- (void)progresssBtnClick:(UISlider *)sender {
DSLog(@"progresssBtnClick:%f", sender.value);
if (self.delegate && [self.delegate respondsToSelector:@selector(didSliderValueChange:)]) {
[self.delegate didSliderValueChange:sender.value];
}
}
- (void)sliderTouchDown:(UISlider *)sender {
DSLog(@"sliderTouchDown");
_tapGesture.enabled = NO;
if (self.delegate && [self.delegate respondsToSelector:@selector(didSliderTouchBegan:)]) {
[self.delegate didSliderTouchBegan:sender.value];
}
}
- (void)sliderTouchUpInSide:(UISlider *)sender {
DSLog(@"sliderTouchUpInSide");
_tapGesture.enabled = YES;
if (self.delegate && [self.delegate respondsToSelector:@selector(didSliderTouchEnded:)]) {
[self.delegate didSliderTouchEnded:sender.value];
}
}
- (void)playerBtnClick:(UIButton *)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(didClickPlayBtn)]) {
[self.delegate didClickPlayBtn];
}
}
#pragma mark - UIGestureRecognizerDelegate
- (void)actionTapGesture:(UITapGestureRecognizer *)sender {
CGPoint touchPoint = [sender locationInView:self.progressV];
CGFloat value = (self.progressV.maximumValue - self.progressV.minimumValue) * (touchPoint.x / self.progressV.width);
float value = (self.progressV.maximumValue - self.progressV.minimumValue) * (touchPoint.x / self.progressV.width);
[self.progressV setValue:value animated:YES];
if (self.delegate && [self.delegate respondsToSelector:@selector(didSliderTouchEnded:)]) {
[self.delegate didSliderTouchEnded:value];
}
DSLog(@"actionTapGestureactionTapGesture:%f", value);
}
#pragma mark - public
- (void)setIsPlaying:(BOOL)isPlaying {
_isPlaying = isPlaying;
self.playerBtn.selected = isPlaying;
}
- (SoundPlayMode)mode {
return self.circleBtn.tag;
}
#pragma mark - lazy
......@@ -195,6 +243,8 @@
_progressV.dk_maximumTrackTintColorPicker = DKColorPickerWithColors(ColorFromHex(0xE3E1E1), ColorFromHex(0x131724), DSWhite);
[_progressV setThumbImage:[UIImage imageNamed:@"muse_slider_thumbImage"] forState:UIControlStateNormal];
[_progressV addTarget:self action:@selector(progresssBtnClick:) forControlEvents:UIControlEventValueChanged];
[_progressV addTarget:self action:@selector(sliderTouchDown:) forControlEvents:UIControlEventTouchDown];
[_progressV addTarget:self action:@selector(sliderTouchUpInSide:) forControlEvents:UIControlEventTouchUpInside];
// 为UISlider添加点击事件
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionTapGesture:)];
......@@ -226,7 +276,8 @@
if (!_playerBtn) {
_playerBtn = [UIButton new];
[_playerBtn addTarget:self action:@selector(playerBtnClick:) forControlEvents:UIControlEventTouchUpInside];
[_playerBtn dk_setBackgroundImage:DKImagePickerWithNames(@"audio_play_icon", @"dk_audio_play_icon", DSWhite) forState:UIControlStateNormal];
[_playerBtn dk_setBackgroundImage:DKImagePickerWithNames(@"audio_play_icon", @"dk_audio_play_icon", @"dk_audio_play_icon") forState:UIControlStateNormal];
[_playerBtn dk_setBackgroundImage:DKImagePickerWithNames(@"audio_pause", @"dk_audio_pause", @"dk_audio_pause") forState:UIControlStateSelected];
}
return _playerBtn;
}
......
//
// TimerProxy.h
// DreamSleep
//
// Created by peter on 2022/5/8.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TimerProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
NS_ASSUME_NONNULL_END
//
// TimerProxy.m
// DreamSleep
//
// Created by peter on 2022/5/8.
//
#import "TimerProxy.h"
@interface TimerProxy ()
@property(nonatomic, weak) id target;
@end
@implementation TimerProxy
+ (instancetype)proxyWithTarget:(id)target {
// 注意:没有init方法
TimerProxy *proxy = [TimerProxy alloc];
proxy.target = target;
return proxy;
}
// NSProxy接收到消息会自动进入到调用这个方法进入消息转发流程
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
......@@ -12,6 +12,7 @@ target 'DreamSleep' do
pod 'YYWebImage', '~> 1.0.5'
pod 'YYImage/WebP'
pod 'YYModel', '~> 1.0.4'
pod 'FreeStreamer', '~> 4.0.0'
end
# AFNetworking (4.0.1)
......@@ -26,3 +27,4 @@ end
# YYImage/WebP(模拟器上目前无法运行)
# YYModel (1.0.4)
# SDWebImage (5.12.5)(去掉)
# FreeStreamer(4.0.0)
......@@ -20,10 +20,13 @@ PODS:
- DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core
- DOUAudioStreamer (0.2.16)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- MJRefresh (3.7.5)
- Reachability (3.2)
- YTKNetwork (3.0.6):
- AFNetworking/NSURLSession (~> 4.0)
- YYCache (1.0.4)
......@@ -40,6 +43,7 @@ PODS:
DEPENDENCIES:
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- FreeStreamer (~> 4.0.0)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MBProgressHUD (~> 1.2.0)
......@@ -54,10 +58,12 @@ SPEC REPOS:
- AFNetworking
- DKNightVersion
- DOUAudioStreamer
- FreeStreamer
- lottie-ios
- Masonry
- MBProgressHUD
- MJRefresh
- Reachability
- YTKNetwork
- YYCache
- YYImage
......@@ -68,16 +74,18 @@ SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
PODFILE CHECKSUM: 5f273d0f03f58db41d7f0a6d3d96a8bd054ab744
PODFILE CHECKSUM: d78d9f7fd55a2a7be3fae24d212bdd5eab78666c
COCOAPODS: 1.11.3
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <Foundation/Foundation.h>
#include "FSAudioStream.h"
@class FSCheckContentTypeRequest;
@class FSParsePlaylistRequest;
@class FSParseRssPodcastFeedRequest;
@class FSPlaylistItem;
@protocol FSAudioControllerDelegate;
/**
* FSAudioController is functionally equivalent to FSAudioStream with
* one addition: it can be directly fed with a playlist (PLS, M3U) URL
* or an RSS podcast feed. It determines the content type and forms
* a playlist for playback. Notice that this generates more traffic and
* is generally more slower than using an FSAudioStream directly.
*
* It is also possible to construct a playlist by yourself by providing
* the playlist items. In this case see the methods for managing the playlist.
*
* If you have a playlist with multiple items, FSAudioController attemps
* automatically preload the next item in the playlist. This helps to
* start the next item playback immediately without the need for the
* user to wait for buffering.
*
* Notice that do not attempt to set your own blocks to the audio stream
* owned by the controller. FSAudioController uses the blocks internally
* and any user set blocks will be overwritten. Instead use the blocks
* offered by FSAudioController.
*/
@interface FSAudioController : NSObject {
NSURL *_url;
NSMutableArray *_streams;
float _volume;
BOOL _readyToPlay;
FSCheckContentTypeRequest *_checkContentTypeRequest;
FSParsePlaylistRequest *_parsePlaylistRequest;
FSParseRssPodcastFeedRequest *_parseRssPodcastFeedRequest;
void (^_onStateChangeBlock)(FSAudioStreamState);
void (^_onMetaDataAvailableBlock)(NSDictionary*);
void (^_onFailureBlock)(FSAudioStreamError error, NSString *errorDescription);
}
/**
* Initializes the audio stream with an URL.
*
* @param url The URL from which the stream data is retrieved.
*/
- (id)initWithUrl:(NSURL *)url;
/**
* Starts playing the stream. Before the playback starts,
* the URL content type is checked and playlists resolved.
*/
- (void)play;
/**
* Starts playing the stream from an URL. Before the playback starts,
* the URL content type is checked and playlists resolved.
*
* @param url The URL from which the stream data is retrieved.
*/
- (void)playFromURL:(NSURL *)url;
/**
* Starts playing the stream from the given playlist. Each item in the array
* must an FSPlaylistItem.
*
* @param playlist The playlist items.
*/
- (void)playFromPlaylist:(NSArray *)playlist;
/**
* Starts playing the stream from the given playlist. Each item in the array
* must an FSPlaylistItem. The playback starts from the given index
* in the playlist.
*
* @param playlist The playlist items.
* @param index The playlist index where to start playback from.
*/
- (void)playFromPlaylist:(NSArray *)playlist itemIndex:(NSUInteger)index;
/**
* Plays a playlist item at the specified index.
*
* @param index The playlist index where to start playback from.
*/
- (void)playItemAtIndex:(NSUInteger)index;
/**
* Returns the count of playlist items.
*/
- (NSUInteger)countOfItems;
/**
* Adds an item to the playlist.
*
* @param item The playlist item to be added.
*/
- (void)addItem:(FSPlaylistItem *)item;
/**
* Adds an item to the playlist at a specific position.
*
* @param item The playlist item to be added.
* @param index The location in the playlist to place the new item
*/
- (void)insertItem:(FSPlaylistItem *)item atIndex:(NSInteger)index;
/**
* Moves an item already in the playlist to a different position in the playlist
*
* @param from The original index of the track to move
* @param to The destination of the the track at the index specified in `from`
*/
- (void)moveItemAtIndex:(NSUInteger)from toIndex:(NSUInteger)to;
/**
* Replaces a playlist item.
*
* @param index The index of the playlist item to be replaced.
* @param item The playlist item used the replace the existing one.
*/
- (void)replaceItemAtIndex:(NSUInteger)index withItem:(FSPlaylistItem *)item;
/**
* Removes a playlist item.
*
* @param index The index of the playlist item to be removed.
*/
- (void)removeItemAtIndex:(NSUInteger)index;
/**
* Stops the stream playback.
*/
- (void)stop;
/**
* If the stream is playing, the stream playback is paused upon calling pause.
* Otherwise (the stream is paused), calling pause will continue the playback.
*/
- (void)pause;
/**
* Returns the playback status: YES if the stream is playing, NO otherwise.
*/
- (BOOL)isPlaying;
/**
* Returns if the current multiple-item playlist has next item
*/
- (BOOL)hasNextItem;
/**
* Returns if the current multiple-item playlist has Previous item
*/
- (BOOL)hasPreviousItem;
/**
* Play the next item of multiple-item playlist
*/
- (void)playNextItem;
/**
* Play the previous item of multiple-item playlist
*/
- (void)playPreviousItem;
/**
* This property holds the current playback volume of the stream,
* from 0.0 to 1.0.
*
* Note that the overall volume is still constrained by the volume
* set by the user! So the actual volume cannot be higher
* than the volume currently set by the user. For example, if
* requesting a volume of 0.5, then the volume will be 50%
* lower than the current playback volume set by the user.
*/
@property (nonatomic,assign) float volume;
/**
* The controller URL.
*/
@property (nonatomic,assign) NSURL *url;
/**
* The the active playing stream, which may change
* from time to time during the playback. In this way, do not
* set your own blocks to the stream but use the blocks
* provides by FSAudioController.
*/
@property (readonly) FSAudioStream *activeStream;
/**
* The playlist item the controller is currently using.
*/
@property (nonatomic,readonly) FSPlaylistItem *currentPlaylistItem;
/**
* This property determines if the next playlist item should be loaded
* automatically. This is YES by default.
*/
@property (nonatomic,assign) BOOL preloadNextPlaylistItemAutomatically;
/**
* This property determines if the debug output is enabled. Disabled
* by default
*/
@property (nonatomic,assign) BOOL enableDebugOutput;
/**
* This property determines if automatic audio session handling is enabled.
* This is YES by default.
*/
@property (nonatomic,assign) BOOL automaticAudioSessionHandlingEnabled;
/**
* This property holds the configuration used for the streaming.
*/
@property (nonatomic,strong) FSStreamConfiguration *configuration;
/**
* Called upon a state change.
*/
@property (copy) void (^onStateChange)(FSAudioStreamState state);
/**
* Called upon a meta data is available.
*/
@property (copy) void (^onMetaDataAvailable)(NSDictionary *metadata);
/**
* Called upon a failure.
*/
@property (copy) void (^onFailure)(FSAudioStreamError error, NSString *errorDescription);
/**
* Delegate.
*/
@property (nonatomic,unsafe_unretained) IBOutlet id<FSAudioControllerDelegate> delegate;
@end
/**
* To check the preloading status, use this delegate.
*/
@protocol FSAudioControllerDelegate <NSObject>
@optional
/**
* Called when the controller wants to start preloading an item. Return YES or NO
* depending if you want this item to be preloaded.
*
* @param audioController The audio controller which is doing the preloading.
* @param stream The stream which is wanted to be preloaded.
*/
- (BOOL)audioController:(FSAudioController *)audioController allowPreloadingForStream:(FSAudioStream *)stream;
/**
* Called when the controller starts to preload an item.
*
* @param audioController The audio controller which is doing the preloading.
* @param stream The stream which is preloaded.
*/
- (void)audioController:(FSAudioController *)audioController preloadStartedForStream:(FSAudioStream *)stream;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <Foundation/Foundation.h>
/**
* Content type format.
*/
typedef NS_ENUM(NSInteger, FSFileFormat) {
/**
* Unknown format.
*/
kFSFileFormatUnknown = 0,
/**
* M3U playlist.
*/
kFSFileFormatM3UPlaylist,
/**
* PLS playlist.
*/
kFSFileFormatPLSPlaylist,
/**
* XML file.
*/
kFSFileFormatXML,
/**
* MP3 file.
*/
kFSFileFormatMP3,
/**
* WAVE file.
*/
kFSFileFormatWAVE,
/**
* AIFC file.
*/
kFSFileFormatAIFC,
/**
* AIFF file.
*/
kFSFileFormatAIFF,
/**
* M4A file.
*/
kFSFileFormatM4A,
/**
* MPEG4 file.
*/
kFSFileFormatMPEG4,
/**
* CAF file.
*/
kFSFileFormatCAF,
/**
* AAC_ADTS file.
*/
kFSFileFormatAAC_ADTS,
/**
* Total number of formats.
*/
kFSFileFormatCount
};
/**
* FSCheckContentTypeRequest is a class for checking the content type
* of a URL. It makes an HTTP HEAD request and parses the header information
* from the server. The resulting format is stored in the format property.
*
* To use the class, define the URL for checking the content type using
* the url property. Then, define the onCompletion and onFailure handlers.
* To start the request, use the start method.
*/
@interface FSCheckContentTypeRequest : NSObject <NSURLSessionDelegate> {
NSURLSessionTask *_task;
FSFileFormat _format;
NSString *_contentType;
BOOL _playlist;
BOOL _xml;
}
/**
* The URL of this request.
*/
@property (nonatomic,copy) NSURL *url;
/**
* Called when the content type determination is completed.
*/
@property (copy) void (^onCompletion)(void);
/**
* Called if the content type determination failed.
*/
@property (copy) void (^onFailure)(void);
/**
* Contains the format of the URL upon completion of the request.
*/
@property (nonatomic,readonly) FSFileFormat format;
/**
* Containts the content type of the URL upon completion of the request.
*/
@property (nonatomic,readonly) NSString *contentType;
/**
* The property is true if the URL contains a playlist.
*/
@property (nonatomic,readonly) BOOL playlist;
/**
* The property is true if the URL contains XML data.
*/
@property (nonatomic,readonly) BOOL xml;
/**
* Starts the request.
*/
- (void)start;
/**
* Cancels the request.
*/
- (void)cancel;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import "FSCheckContentTypeRequest.h"
@interface FSCheckContentTypeRequest ()
- (BOOL)guessContentTypeByUrl:(NSURLResponse *)response;
@end
@implementation FSCheckContentTypeRequest
- (id)init
{
self = [super init];
if (self) {
_format = kFSFileFormatUnknown;
_playlist = NO;
_xml = NO;
}
return self;
}
- (void)start
{
if (_task) {
return;
}
_format = kFSFileFormatUnknown;
_playlist = NO;
_contentType = @"";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:10.0];
[request setHTTPMethod:@"HEAD"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
@synchronized (self) {
_task = [session dataTaskWithRequest:request];
}
[_task resume];
if (!_task) {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSCheckContentTypeRequest: Unable to open connection for URL: %@", _url);
#endif
self.onFailure();
return;
}
}
- (void)cancel
{
if (!_task) {
return;
}
@synchronized (self) {
[_task cancel];
_task = nil;
}
}
/*
* =======================================
* Properties
* =======================================
*/
- (FSFileFormat)format
{
return _format;
}
- (NSString *)contentType
{
return _contentType;
}
- (BOOL)playlist
{
return _playlist;
}
- (BOOL)xml
{
return _xml;
}
/*
* =======================================
* NSURLSessionDelegate
* =======================================
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
_contentType = response.MIMEType;
_format = kFSFileFormatUnknown;
_playlist = NO;
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode >= 200 && statusCode <= 299) {
// Only use the content type if the response indicated success (2xx)
if ([_contentType isEqualToString:@"audio/mpeg"]) {
_format = kFSFileFormatMP3;
} else if ([_contentType isEqualToString:@"audio/x-wav"]) {
_format = kFSFileFormatWAVE;
} else if ([_contentType isEqualToString:@"audio/x-aifc"]) {
_format = kFSFileFormatAIFC;
} else if ([_contentType isEqualToString:@"audio/x-aiff"]) {
_format = kFSFileFormatAIFF;
} else if ([_contentType isEqualToString:@"audio/x-m4a"]) {
_format = kFSFileFormatM4A;
} else if ([_contentType isEqualToString:@"audio/mp4"]) {
_format = kFSFileFormatMPEG4;
} else if ([_contentType isEqualToString:@"audio/x-caf"]) {
_format = kFSFileFormatCAF;
} else if ([_contentType isEqualToString:@"audio/aac"] ||
[_contentType isEqualToString:@"audio/aacp"]) {
_format = kFSFileFormatAAC_ADTS;
} else if ([_contentType isEqualToString:@"audio/x-mpegurl"] ||
[_contentType isEqualToString:@"application/x-mpegurl"]) {
_format = kFSFileFormatM3UPlaylist;
_playlist = YES;
} else if ([_contentType isEqualToString:@"audio/x-scpls"] ||
[_contentType isEqualToString:@"application/pls+xml"]) {
_format = kFSFileFormatPLSPlaylist;
_playlist = YES;
} else if ([_contentType isEqualToString:@"text/xml"] ||
[_contentType isEqualToString:@"application/xml"]) {
_format = kFSFileFormatXML;
_xml = YES;
} else {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSCheckContentTypeRequest: Cannot resolve %@, guessing the content type by URL: %@", _contentType, _url);
#endif
[self guessContentTypeByUrl:response];
}
} else {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSCheckContentTypeRequest: Invalid HTTP status code received %li, guessing the content type by URL: %@", (long)statusCode, _url);
#endif
[self guessContentTypeByUrl:response];
}
_task = nil;
self.onCompletion();
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
@synchronized (self) {
_task = nil;
_format = kFSFileFormatUnknown;
_playlist = NO;
}
// Still, try if we could resolve the content type by the URL
if ([self guessContentTypeByUrl:nil]) {
self.onCompletion();
} else {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSCheckContentTypeRequest: Unable to determine content-type for the URL: %@, error %@", _url, [error localizedDescription]);
#endif
self.onFailure();
}
}
/*
* =======================================
* Private
* =======================================
*/
- (BOOL)guessContentTypeByUrl:(NSURLResponse *)response
{
/* The server did not provide meaningful content type;
last resort: check the file suffix, if there is one */
NSString *absoluteUrl;
if (response) {
absoluteUrl = [response.URL absoluteString];
} else {
absoluteUrl = [_url absoluteString];
}
if ([absoluteUrl hasSuffix:@".mp3"]) {
_format = kFSFileFormatMP3;
} else if ([absoluteUrl hasSuffix:@".mp4"]) {
_format = kFSFileFormatMPEG4;
} else if ([absoluteUrl hasSuffix:@".m3u"]) {
_format = kFSFileFormatM3UPlaylist;
_playlist = YES;
} else if ([absoluteUrl hasSuffix:@".pls"]) {
_format = kFSFileFormatPLSPlaylist;
_playlist = YES;
} else if ([absoluteUrl hasSuffix:@".xml"]) {
_format = kFSFileFormatXML;
_xml = YES;
} else {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSCheckContentTypeRequest: Failed to determine content type from the URL: %@", _url);
#endif
/*
* Failed to guess the content type based on the URL.
*/
return NO;
}
/*
* We have determined a content-type.
*/
return YES;
}
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <Foundation/Foundation.h>
/**
* The playlist format.
*/
typedef NS_ENUM(NSInteger, FSPlaylistFormat) {
/**
* Unknown playlist format.
*/
kFSPlaylistFormatNone,
/**
* M3U playlist.
*/
kFSPlaylistFormatM3U,
/**
* PLS playlist.
*/
kFSPlaylistFormatPLS
};
/**
* FSParsePlaylistRequest is a class for parsing a playlist. It supports
* the M3U and PLS formats.
*
* To use the class, define the URL for retrieving the playlist using
* the url property. Then, define the onCompletion and onFailure handlers.
* To start the request, use the start method.
*/
@interface FSParsePlaylistRequest : NSObject<NSURLSessionDelegate> {
NSURLSessionTask *_task;
NSInteger _httpStatus;
NSMutableData *_receivedData;
NSMutableArray *_playlistItems;
FSPlaylistFormat _format;
}
/**
* The URL of this request.
*/
@property (nonatomic,copy) NSURL *url;
/**
* Called when the playlist parsing is completed.
*/
@property (copy) void (^onCompletion)(void);
/**
* Called if the playlist parsing failed.
*/
@property (copy) void (^onFailure)(void);
/**
* The playlist items stored in the FSPlaylistItem class.
*/
@property (readonly) NSMutableArray *playlistItems;
/**
* Starts the request.
*/
- (void)start;
/**
* Cancels the request.
*/
- (void)cancel;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import "FSParsePlaylistRequest.h"
#import "FSPlaylistItem.h"
@interface FSParsePlaylistRequest ()
- (void)parsePlaylistFromData:(NSData *)data;
- (void)parsePlaylistM3U:(NSString *)playlist;
- (void)parsePlaylistPLS:(NSString *)playlist;
- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl;
@property (readonly) FSPlaylistFormat format;
@end
@implementation FSParsePlaylistRequest
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
- (void)start
{
if (_task) {
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:self.url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
@synchronized (self) {
_receivedData = [NSMutableData data];
_task = [session dataTaskWithRequest:request];
_playlistItems = [[NSMutableArray alloc] init];
_format = kFSPlaylistFormatNone;
}
[_task resume];
}
- (void)cancel
{
if (!_task) {
return;
}
@synchronized (self) {
[_task cancel];
_task = nil;
}
}
/*
* =======================================
* Properties
* =======================================
*/
- (NSMutableArray *)playlistItems
{
return [_playlistItems copy];
}
- (FSPlaylistFormat)format
{
return _format;
}
/*
* =======================================
* Private
* =======================================
*/
- (void)parsePlaylistFromData:(NSData *)data
{
NSString *playlistData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
if (_format == kFSPlaylistFormatM3U) {
[self parsePlaylistM3U:playlistData];
if ([_playlistItems count] == 0) {
// If we failed to grab any playlist items, still try
// to parse it in another format; perhaps the server
// mistakingly identified the playlist format
[self parsePlaylistPLS:playlistData];
}
} else if (_format == kFSPlaylistFormatPLS) {
[self parsePlaylistPLS:playlistData];
if ([_playlistItems count] == 0) {
// If we failed to grab any playlist items, still try
// to parse it in another format; perhaps the server
// mistakingly identified the playlist format
[self parsePlaylistM3U:playlistData];
}
}
if ([_playlistItems count] == 0) {
/*
* Fail if we failed to parse any items from the playlist.
*/
self.onFailure();
}
}
- (void)parsePlaylistM3U:(NSString *)playlist
{
[_playlistItems removeAllObjects];
for (NSString *line in [playlist componentsSeparatedByString:@"\n"]) {
if ([line hasPrefix:@"#"]) {
/* metadata, skip */
continue;
}
if ([line hasPrefix:@"http://"] ||
[line hasPrefix:@"https://"]) {
FSPlaylistItem *item = [[FSPlaylistItem alloc] init];
item.url = [NSURL URLWithString:[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
[_playlistItems addObject:item];
} else if ([line hasPrefix:@"file://"]) {
FSPlaylistItem *item = [[FSPlaylistItem alloc] init];
item.url = [self parseLocalFileUrl:line];
[_playlistItems addObject:item];
}
}
}
- (void)parsePlaylistPLS:(NSString *)playlist
{
[_playlistItems removeAllObjects];
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
size_t i = 0;
for (NSString *rawLine in [playlist componentsSeparatedByString:@"\n"]) {
NSString *line = [rawLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (i == 0) {
if ([[line lowercaseString] hasPrefix:@"[playlist]"]) {
i++;
continue;
} else {
// Invalid playlist; the first line should indicate that this is a playlist
return;
}
}
// Ignore empty lines
if ([line length] == 0) {
i++;
continue;
}
// Not an empty line; so expect that this is a key/value pair
NSRange r = [line rangeOfString:@"="];
// Invalid format, key/value pair not found
if (r.length == 0) {
return;
}
NSString *key = [[line substringToIndex:r.location] lowercaseString];
NSString *value = [line substringFromIndex:r.location + 1];
props[key] = value;
i++;
}
NSInteger numItems = [[props valueForKey:@"numberofentries"] integerValue];
if (numItems == 0) {
// Invalid playlist; number of playlist items not defined
return;
}
for (i=0; i < numItems; i++) {
FSPlaylistItem *item = [[FSPlaylistItem alloc] init];
NSString *title = [props valueForKey:[NSString stringWithFormat:@"title%lu", (i+1)]];
item.title = title;
NSString *file = [props valueForKey:[NSString stringWithFormat:@"file%lu", (i+1)]];
if ([file hasPrefix:@"http://"] ||
[file hasPrefix:@"https://"]) {
item.url = [NSURL URLWithString:file];
[_playlistItems addObject:item];
} else if ([file hasPrefix:@"file://"]) {
item.url = [self parseLocalFileUrl:file];
[_playlistItems addObject:item];
}
}
}
- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl
{
// Resolve the local bundle URL
NSString *path = [fileUrl substringFromIndex:7];
NSRange range = [path rangeOfString:@"." options:NSBackwardsSearch];
NSString *fileName = [path substringWithRange:NSMakeRange(0, range.location)];
NSString *suffix = [path substringWithRange:NSMakeRange(range.location + 1, [path length] - [fileName length] - 1)];
return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:suffix]];
}
/*
* =======================================
* NSURLSessionDelegate
* =======================================
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
_httpStatus = [httpResponse statusCode];
NSString *contentType = response.MIMEType;
NSString *absoluteUrl = [response.URL absoluteString];
_format = kFSPlaylistFormatNone;
if ([contentType isEqualToString:@"audio/x-mpegurl"] ||
[contentType isEqualToString:@"application/x-mpegurl"]) {
_format = kFSPlaylistFormatM3U;
} else if ([contentType isEqualToString:@"audio/x-scpls"] ||
[contentType isEqualToString:@"application/pls+xml"]) {
_format = kFSPlaylistFormatPLS;
} else if ([contentType isEqualToString:@"text/plain"]) {
/* The server did not provide meaningful content type;
last resort: check the file suffix, if there is one */
if ([absoluteUrl hasSuffix:@".m3u"]) {
_format = kFSPlaylistFormatM3U;
} else if ([absoluteUrl hasSuffix:@".pls"]) {
_format = kFSPlaylistFormatPLS;
}
}
if (_format == kFSPlaylistFormatNone) {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSParsePlaylistRequest: Unable to determine the type of the playlist for URL: %@", _url);
#endif
self.onFailure();
} else {
completionHandler(NSURLSessionResponseAllow);
}
[_receivedData setLength:0];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
// Resume the Download Task manually because apparently iOS does not do it automatically?!
[downloadTask resume];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[_receivedData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
if(error) {
@synchronized (self) {
_task = nil;
_receivedData = nil;
}
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSParsePlaylistRequest: Connection failed for URL: %@, error %@", _url, [error localizedDescription]);
#endif
self.onFailure();
} else {
@synchronized (self) {
_task = nil;
}
if (_httpStatus != 200) {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSParsePlaylistRequest: Unable to receive playlist from URL: %@", _url);
#endif
self.onFailure();
return;
}
[self parsePlaylistFromData:_receivedData];
self.onCompletion();
}
}
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import "FSXMLHttpRequest.h"
/**
* Use this request for retrieving the contents for a podcast RSS feed.
* Upon request completion, the resulting playlist items are
* in the playlistItems property.
*
* See the FSXMLHttpRequest class how to form a request to retrieve
* the RSS feed.
*/
@interface FSParseRssPodcastFeedRequest : FSXMLHttpRequest {
NSMutableArray *_playlistItems;
}
/**
* The playlist items stored in the FSPlaylistItem class.
*/
@property (readonly) NSMutableArray *playlistItems;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <libxml/parser.h>
#import <libxml/xpath.h>
#import "FSParseRssPodcastFeedRequest.h"
#import "FSPlaylistItem.h"
static NSString *const kXPathQueryItems = @"/rss/channel/item";
@interface FSParseRssPodcastFeedRequest ()
- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl;
- (void)parseItems:(xmlNodePtr)node;
@end
@implementation FSParseRssPodcastFeedRequest
- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl
{
// Resolve the local bundle URL
NSString *path = [fileUrl substringFromIndex:7];
NSRange range = [path rangeOfString:@"." options:NSBackwardsSearch];
NSString *fileName = [path substringWithRange:NSMakeRange(0, range.location)];
NSString *suffix = [path substringWithRange:NSMakeRange(range.location + 1, [path length] - [fileName length] - 1)];
return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:suffix]];
}
- (void)parseItems:(xmlNodePtr)node
{
FSPlaylistItem *item = [[FSPlaylistItem alloc] init];
for (xmlNodePtr n = node->children; n != NULL; n = n->next) {
NSString *nodeName = @((const char *)n->name);
if ([nodeName isEqualToString:@"title"]) {
item.title = [self contentForNode:n];
} else if ([nodeName isEqualToString:@"enclosure"]) {
NSString *url = [self contentForNodeAttribute:n attribute:"url"];
if ([url hasPrefix:@"file://"]) {
item.url = [self parseLocalFileUrl:url];
} else {
item.url = [NSURL URLWithString:url];
}
} else if ([nodeName isEqualToString:@"link"]) {
NSString *url = [self contentForNode:n];
if ([url hasPrefix:@"file://"]) {
item.originatingUrl = [self parseLocalFileUrl:url];
} else {
item.originatingUrl = [NSURL URLWithString:url];
}
}
}
if (nil == item.url &&
nil == item.originatingUrl) {
// Not a valid item, as there is no URL. Skip.
return;
}
[_playlistItems addObject:item];
}
- (void)parseResponseData
{
if (!_playlistItems) {
_playlistItems = [[NSMutableArray alloc] init];
}
[_playlistItems removeAllObjects];
// RSS feed publication date format:
// Sun, 22 Jul 2012 17:35:05 GMT
[_dateFormatter setDateFormat:@"EEE, dd MMMM yyyy HH:mm:ss V"];
[_dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]];
[self performXPathQuery:kXPathQueryItems];
}
- (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery
{
if ([xPathQuery isEqualToString:kXPathQueryItems]) {
[self parseItems:node];
}
}
- (NSArray *)playlistItems
{
return _playlistItems;
}
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <Foundation/Foundation.h>
/**
* A playlist item. Each item has a title and url.
*/
@interface FSPlaylistItem : NSObject {
}
/**
* The title of the playlist item.
*/
@property (nonatomic,copy) NSString *title;
/**
* The URL of the playlist item.
*/
@property (nonatomic,copy) NSURL *url;
/**
* The originating URL of the playlist item.
*/
@property (nonatomic,copy) NSURL *originatingUrl;
/**
* The number of bytes of audio data. Notice that this may differ
* from the number of bytes the server returns for the content length!
* For instance audio file meta data is excluded from the count.
* Effectively you can use this property for seeking calculations.
*
* The property is only available for non-continuous streams which
* have been in the "playing" state.
*/
@property (nonatomic,assign) UInt64 audioDataByteCount;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import "FSPlaylistItem.h"
@implementation FSPlaylistItem
- (BOOL)isEqual:(id)anObject
{
FSPlaylistItem *otherObject = anObject;
if ([otherObject.title isEqual:self.title] &&
[otherObject.url isEqual:self.url]) {
return YES;
}
return NO;
}
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import <Foundation/Foundation.h>
typedef struct _xmlDoc xmlDoc;
typedef xmlDoc *xmlDocPtr;
typedef struct _xmlNode xmlNode;
typedef xmlNode *xmlNodePtr;
/**
* XML HTTP request error status.
*/
typedef NS_ENUM(NSInteger, FSXMLHttpRequestError) {
/**
* No error.
*/
FSXMLHttpRequestError_NoError = 0,
/**
* Connection failed.
*/
FSXMLHttpRequestError_Connection_Failed,
/**
* Invalid HTTP status.
*/
FSXMLHttpRequestError_Invalid_Http_Status,
/**
* XML parser failed.
*/
FSXMLHttpRequestError_XML_Parser_Failed
};
/**
* FSXMLHttpRequest is a class for retrieving data in the XML
* format over a HTTP or HTTPS connection. It provides
* the necessary foundation for parsing the retrieved XML data.
* This class is not meant to be used directly but subclassed
* to a specific requests.
*
* The usage pattern is the following:
*
* 1. Specify the URL with the url property.
* 2. Define the onCompletion and onFailure handlers.
* 3. Call the start method.
*/
@interface FSXMLHttpRequest : NSObject {
NSURLSessionTask *_task;
xmlDocPtr _xmlDocument;
NSDateFormatter *_dateFormatter;
}
/**
* The URL of the request.
*/
@property (nonatomic,copy) NSURL *url;
/**
* Called upon completion of the request.
*/
@property (copy) void (^onCompletion)(void);
/**
* Called upon a failure.
*/
@property (copy) void (^onFailure)(void);
/**
* If the request fails, contains the latest error status.
*/
@property (readonly) FSXMLHttpRequestError lastError;
/**
* Starts the request.
*/
- (void)start;
/**
* Cancels the request.
*/
- (void)cancel;
/**
* Performs an XPath query on the parsed XML data.
* Yields a parseXMLNode method call, which must be
* defined in the subclasses.
*
* @param query The XPath query to be performed.
*/
- (NSArray *)performXPathQuery:(NSString *)query;
/**
* Retrieves content for the given XML node.
*
* @param node The node for content retreval.
*/
- (NSString *)contentForNode:(xmlNodePtr)node;
/**
* Retrieves content for the given XML node attribute.
*
* @param node The node for content retrieval.
* @param attr The attribute from which the content is retrieved.
*/
- (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr;
/**
* Retrieves date from the given XML node.
*
* @param node The node for retrieving the date.
*/
- (NSDate *)dateFromNode:(xmlNodePtr)node;
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#import "FSXMLHttpRequest.h"
#import <libxml/parser.h>
#import <libxml/xpath.h>
#define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit)
#define CURRENT_CALENDAR [NSCalendar currentCalendar]
@interface FSXMLHttpRequest (PrivateMethods)
- (const char *)detectEncoding;
- (void)parseResponseData;
- (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery;
@end
@implementation FSXMLHttpRequest
- (id)init
{
self = [super init];
if (self) {
_dateFormatter = [[NSDateFormatter alloc] init];
}
return self;
}
- (void)start
{
if (_task) {
return;
}
_lastError = FSXMLHttpRequestError_NoError;
NSURLRequest *request = [NSURLRequest requestWithURL:self.url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLSession *session = [NSURLSession sharedSession];
__weak FSXMLHttpRequest *weakSelf = self;
@synchronized (self) {
_task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
FSXMLHttpRequest *strongSelf = weakSelf;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if(error) {
strongSelf->_lastError = FSXMLHttpRequestError_Connection_Failed;
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSXMLHttpRequest: Request failed for URL: %@, error %@", strongSelf.url, [error localizedDescription]);
#endif
dispatch_async(dispatch_get_main_queue(), ^(){
strongSelf.onFailure();
});
} else {
if (httpResponse.statusCode != 200) {
strongSelf->_lastError = FSXMLHttpRequestError_Invalid_Http_Status;
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSXMLHttpRequest: Unable to receive content for URL: %@", strongSelf.url);
#endif
dispatch_async(dispatch_get_main_queue(), ^(){
strongSelf.onFailure();
});
return;
}
const char *encoding = [self detectEncoding:data];
strongSelf->_xmlDocument = xmlReadMemory([data bytes],
(int)[data length],
"",
encoding,
0);
if (!strongSelf->_xmlDocument) {
strongSelf->_lastError = FSXMLHttpRequestError_XML_Parser_Failed;
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSXMLHttpRequest: Unable to parse the content for URL: %@", strongSelf.url);
#endif
dispatch_async(dispatch_get_main_queue(), ^(){
strongSelf.onFailure();
});
return;
}
[strongSelf parseResponseData];
xmlFreeDoc(strongSelf->_xmlDocument);
strongSelf->_xmlDocument = nil;
dispatch_async(dispatch_get_main_queue(), ^(){
strongSelf.onCompletion();
});
}
}];
}
[_task resume];
}
- (void)cancel
{
if (!_task) {
return;
}
@synchronized (self) {
[_task cancel];
_task = nil;
}
}
/*
* =======================================
* XML handling
* =======================================
*/
- (NSArray *)performXPathQuery:(NSString *)query
{
NSMutableArray *resultNodes = [NSMutableArray array];
xmlXPathContextPtr xpathCtx = NULL;
xmlXPathObjectPtr xpathObj = NULL;
xpathCtx = xmlXPathNewContext(_xmlDocument);
if (xpathCtx == NULL) {
goto cleanup;
}
xpathObj = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], xpathCtx);
if (xpathObj == NULL) {
goto cleanup;
}
xmlNodeSetPtr nodes = xpathObj->nodesetval;
if (!nodes) {
goto cleanup;
}
for (size_t i = 0; i < nodes->nodeNr; i++) {
[self parseXMLNode:nodes->nodeTab[i] xPathQuery:query];
}
cleanup:
if (xpathObj) {
xmlXPathFreeObject(xpathObj);
}
if (xpathCtx) {
xmlXPathFreeContext(xpathCtx);
}
return resultNodes;
}
- (NSString *)contentForNode:(xmlNodePtr)node
{
NSString *stringWithContent;
if (!node) {
stringWithContent = [[NSString alloc] init];
} else {
xmlChar *content = xmlNodeGetContent(node);
if (!content) {
return stringWithContent;
}
stringWithContent = @((const char *)content);
xmlFree(content);
}
return stringWithContent;
}
- (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr
{
NSString *stringWithContent;
if (!node) {
stringWithContent = [[NSString alloc] init];
} else {
xmlChar *content = xmlGetProp(node, (const xmlChar *)attr);
if (!content) {
return stringWithContent;
}
stringWithContent = @((const char *)content);
xmlFree(content);
}
return stringWithContent;
}
/*
* =======================================
* Helpers
* =======================================
*/
- (const char *)detectEncoding:(NSData *)receivedData
{
const char *encoding = 0;
const char *header = strndup([receivedData bytes], 60);
if (strstr(header, "utf-8") || strstr(header, "UTF-8")) {
encoding = "UTF-8";
} else if (strstr(header, "iso-8859-1") || strstr(header, "ISO-8859-1")) {
encoding = "ISO-8859-1";
}
free((void *)header);
return encoding;
}
- (NSDate *)dateFromNode:(xmlNodePtr)node
{
NSString *dateString = [self contentForNode:node];
/*
* For some NSDateFormatter date parsing oddities: http://www.openradar.me/9944011
*
* Engineering has determined that this issue behaves as intended based on the following information:
*
* This is an intentional change in iOS 5. The issue is this: With the short formats as specified by z (=zzz) or v (=vvv),
* there can be a lot of ambiguity. For example, "ET" for Eastern Time" could apply to different time zones in many different regions.
* To improve formatting and parsing reliability, the short forms are only used in a locale if the "cu" (commonly used) flag is set
* for the locale. Otherwise, only the long forms are used (for both formatting and parsing). This is a change in
* open-source CLDR 2.0 / ICU 4.8, which is the basis for the ICU in iOS 5, which in turn is the basis of NSDateFormatter behavior.
*
* For the "en" locale (= "en_US"), the cu flag is set for metazones such as Alaska, America_Central, America_Eastern, America_Mountain,
* America_Pacific, Atlantic, Hawaii_Aleutian, and GMT. It is not set for Europe_Central.
*
* However, for the "en_GB" locale, the cu flag is set for Europe_Central.
*
* So, a formatter set for short timezone style "z" or "zzz" and locale "en" or "en_US" will not parse "CEST" or "CET", but if the
* locale is instead set to "en_GB" it will parse those. The "GMT" style will be parsed by all.
*
* If the formatter is set for the long timezone style "zzzz", and the locale is any of "en", "en_US", or "en_GB", then any of the
* following will be parsed, because they are unambiguous:
*
* "Pacific Daylight Time" "Central European Summer Time" "Central European Time"
*
*/
return [_dateFormatter dateFromString:dateString];
}
@end
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_AUDIO_QUEUE_H
#define ASTREAMER_AUDIO_QUEUE_H
#include <AudioToolbox/AudioToolbox.h> /* AudioFileStreamID */
namespace astreamer {
class Audio_Queue_Delegate;
struct queued_packet;
class Audio_Queue {
public:
Audio_Queue_Delegate *m_delegate;
enum State {
IDLE,
RUNNING,
PAUSED
};
Audio_Queue();
virtual ~Audio_Queue();
bool initialized();
void init();
// Notice: the queue blocks if it has no free buffers
void handleAudioPackets(UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);
void start();
void pause();
void stop(bool stopImmediately);
void stop();
float volume();
void setVolume(float volume);
void setPlayRate(float playRate);
AudioTimeStamp currentTime();
AudioQueueLevelMeterState levels();
private:
Audio_Queue(const Audio_Queue&);
Audio_Queue& operator=(const Audio_Queue&);
State m_state;
AudioQueueRef m_outAQ; // the audio queue
AudioQueueBufferRef *m_audioQueueBuffer; // audio queue buffers
AudioStreamPacketDescription *m_packetDescs; // packet descriptions for enqueuing audio
UInt32 m_fillBufferIndex; // the index of the audioQueueBuffer that is being filled
UInt32 m_bytesFilled; // how many bytes have been filled
UInt32 m_packetsFilled; // how many packets have been filled
UInt32 m_buffersUsed; // how many buffers are used
bool m_audioQueueStarted; // flag to indicate that the queue has been started
bool *m_bufferInUse; // flags to indicate that a buffer is still in use
bool m_levelMeteringEnabled;
pthread_mutex_t m_mutex;
pthread_mutex_t m_bufferInUseMutex;
pthread_cond_t m_bufferFreeCondition;
public:
OSStatus m_lastError;
AudioStreamBasicDescription m_streamDesc;
float m_initialOutputVolume;
private:
void cleanup();
void setCookiesForStream(AudioFileStreamID inAudioFileStream);
void setState(State state);
void enqueueBuffer();
static void audioQueueOutputCallback(void *inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
static void audioQueueIsRunningCallback(void *inClientData, AudioQueueRef inAQ, AudioQueuePropertyID inID);
};
class Audio_Queue_Delegate {
public:
virtual void audioQueueStateChanged(Audio_Queue::State state) = 0;
virtual void audioQueueBuffersEmpty() = 0;
virtual void audioQueueInitializationFailed() = 0;
virtual void audioQueueFinishedPlayingPacket() = 0;
};
} // namespace astreamer
#endif // ASTREAMER_AUDIO_QUEUE_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_AUDIO_STREAM_H
#define ASTREAMER_AUDIO_STREAM_H
#import "input_stream.h"
#include "audio_queue.h"
#include <AudioToolbox/AudioToolbox.h>
#include <list>
namespace astreamer {
typedef struct queued_packet {
UInt64 identifier;
AudioStreamPacketDescription desc;
struct queued_packet *next;
char data[];
} queued_packet_t;
typedef struct {
float offset;
float timePlayed;
} AS_Playback_Position;
enum Audio_Stream_Error {
AS_ERR_OPEN = 1, // Cannot open the audio stream
AS_ERR_STREAM_PARSE = 2, // Parse error
AS_ERR_NETWORK = 3, // Network error
AS_ERR_UNSUPPORTED_FORMAT = 4,
AS_ERR_BOUNCING = 5,
AS_ERR_TERMINATED = 6
};
class Audio_Stream_Delegate;
class File_Output;
#define kAudioStreamBitrateBufferSize 50
class Audio_Stream : public Input_Stream_Delegate, public Audio_Queue_Delegate {
public:
Audio_Stream_Delegate *m_delegate;
enum State {
STOPPED,
BUFFERING,
PLAYING,
PAUSED,
SEEKING,
FAILED,
END_OF_FILE,
PLAYBACK_COMPLETED
};
Audio_Stream();
virtual ~Audio_Stream();
void open();
void open(Input_Stream_Position *position);
void close(bool closeParser);
void pause();
void rewind(unsigned seconds);
void startCachedDataPlayback();
AS_Playback_Position playbackPosition();
UInt64 audioDataByteCount();
float durationInSeconds();
void seekToOffset(float offset);
Input_Stream_Position streamPositionForOffset(float offset);
float currentVolume();
void setDecoderRunState(bool decoderShouldRun);
void setVolume(float volume);
void setPlayRate(float playRate);
void setUrl(CFURLRef url);
void setStrictContentTypeChecking(bool strictChecking);
void setDefaultContentType(CFStringRef defaultContentType);
void setSeekOffset(float offset);
void setDefaultContentLength(UInt64 defaultContentLength);
void setContentLength(UInt64 contentLength);
void setPreloading(bool preloading);
bool isPreloading();
void setOutputFile(CFURLRef url);
CFURLRef outputFile();
State state();
CFStringRef sourceFormatDescription();
CFStringRef contentType();
CFStringRef createCacheIdentifierForURL(CFURLRef url);
size_t cachedDataSize();
bool strictContentTypeChecking();
float bitrate();
UInt64 defaultContentLength();
UInt64 contentLength();
int playbackDataCount();
AudioQueueLevelMeterState levels();
/* Audio_Queue_Delegate */
void audioQueueStateChanged(Audio_Queue::State state);
void audioQueueBuffersEmpty();
void audioQueueInitializationFailed();
void audioQueueFinishedPlayingPacket();
/* Input_Stream_Delegate */
void streamIsReadyRead();
void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes);
void streamEndEncountered();
void streamErrorOccurred(CFStringRef errorDesc);
void streamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes);
private:
Audio_Stream(const Audio_Stream&);
Audio_Stream& operator=(const Audio_Stream&);
bool m_inputStreamRunning;
bool m_audioStreamParserRunning;
bool m_initialBufferingCompleted;
bool m_discontinuity;
bool m_preloading;
bool m_audioQueueConsumedPackets;
UInt64 m_defaultContentLength;
UInt64 m_contentLength;
UInt64 m_originalContentLength;
UInt64 m_bytesReceived;
State m_state;
Input_Stream *m_inputStream;
Audio_Queue *m_audioQueue;
CFRunLoopTimerRef m_watchdogTimer;
CFRunLoopTimerRef m_seekTimer;
CFRunLoopTimerRef m_inputStreamTimer;
CFRunLoopTimerRef m_stateSetTimer;
CFRunLoopTimerRef m_decodeTimer;
AudioFileStreamID m_audioFileStream; // the audio file stream parser
AudioConverterRef m_audioConverter;
AudioStreamBasicDescription m_srcFormat;
AudioStreamBasicDescription m_dstFormat;
OSStatus m_initializationError;
UInt32 m_outputBufferSize;
UInt8 *m_outputBuffer;
UInt64 m_packetIdentifier;
UInt64 m_playingPacketIdentifier;
UInt64 m_dataOffset;
float m_seekOffset;
size_t m_bounceCount;
CFAbsoluteTime m_firstBufferingTime;
bool m_strictContentTypeChecking;
CFStringRef m_defaultContentType;
CFStringRef m_contentType;
File_Output *m_fileOutput;
CFURLRef m_outputFile;
queued_packet_t *m_queuedHead;
queued_packet_t *m_queuedTail;
queued_packet_t *m_playPacket;
std::list <queued_packet_t*> m_processedPackets;
unsigned m_numPacketsToRewind;
size_t m_cachedDataSize;
UInt64 m_audioDataByteCount;
UInt64 m_audioDataPacketCount;
UInt32 m_bitRate;
UInt32 m_metaDataSizeInBytes;
double m_packetDuration;
double m_bitrateBuffer[kAudioStreamBitrateBufferSize];
size_t m_bitrateBufferIndex;
float m_outputVolume;
bool m_converterRunOutOfData;
bool m_decoderShouldRun;
bool m_decoderFailed;
bool m_decoderThreadCreated;
pthread_mutex_t m_packetQueueMutex;
pthread_mutex_t m_streamStateMutex;
pthread_t m_decodeThread;
CFRunLoopRef m_decodeRunLoop;
CFRunLoopRef m_mainRunLoop;
CFStringRef createHashForString(CFStringRef str);
Audio_Queue *audioQueue();
void closeAudioQueue();
void closeAndSignalError(int error, CFStringRef errorDescription);
void setState(State state);
void setCookiesForStream(AudioFileStreamID inAudioFileStream);
void createWatchdogTimer();
void invalidateWatchdogTimer();
int cachedDataCount();
void determineBufferingLimits();
void cleanupCachedData();
static void watchdogTimerCallback(CFRunLoopTimerRef timer, void *info);
static void seekTimerCallback(CFRunLoopTimerRef timer, void *info);
static void inputStreamTimerCallback(CFRunLoopTimerRef timer, void *info);
static void stateSetTimerCallback(CFRunLoopTimerRef timer, void *info);
bool decoderShouldRun();
static void decodeSinglePacket(CFRunLoopTimerRef timer, void *info);
static void *decodeLoop(void *arg);
static OSStatus encoderDataCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData);
static void propertyValueCallback(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags);
static void streamDataCallback(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);
AudioFileTypeID audioStreamTypeFromContentType(CFStringRef contentType);
};
class Audio_Stream_Delegate {
public:
virtual void audioStreamStateChanged(Audio_Stream::State state) = 0;
virtual void audioStreamErrorOccurred(int errorCode, CFStringRef errorDescription) = 0;
virtual void audioStreamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData) = 0;
virtual void samplesAvailable(AudioBufferList *samples, UInt32 frames, AudioStreamPacketDescription description) = 0;
virtual void bitrateAvailable() = 0;
};
} // namespace astreamer
#endif // ASTREAMER_AUDIO_STREAM_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_CACHING_STREAM_H
#define ASTREAMER_CACHING_STREAM_H
#include "input_stream.h"
namespace astreamer {
class File_Output;
class File_Stream;
class Caching_Stream : public Input_Stream, public Input_Stream_Delegate {
private:
Input_Stream *m_target;
File_Output *m_fileOutput;
File_Stream *m_fileStream;
bool m_cacheable;
bool m_writable;
bool m_useCache;
bool m_cacheMetaDataWritten;
CFStringRef m_cacheIdentifier;
CFURLRef m_fileUrl;
CFURLRef m_metaDataUrl;
private:
CFURLRef createFileURLWithPath(CFStringRef path);
void readMetaData();
public:
Caching_Stream(Input_Stream *target);
virtual ~Caching_Stream();
Input_Stream_Position position();
CFStringRef contentType();
size_t contentLength();
bool open();
bool open(const Input_Stream_Position& position);
void close();
void setScheduledInRunLoop(bool scheduledInRunLoop);
void setUrl(CFURLRef url);
void setCacheIdentifier(CFStringRef cacheIdentifier);
static bool canHandleUrl(CFURLRef url);
/* ID3_Parser_Delegate */
void id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
void id3tagSizeAvailable(UInt32 tagSize);
void streamIsReadyRead();
void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes);
void streamEndEncountered();
void streamErrorOccurred(CFStringRef errorDesc);
void streamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes);
};
} // namespace astreamer
#endif /* ASTREAMER_CACHING_STREAM_H */
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#include "file_output.h"
namespace astreamer {
File_Output::File_Output(CFURLRef fileURL) :
m_writeStream(CFWriteStreamCreateWithFile(kCFAllocatorDefault, fileURL))
{
CFWriteStreamOpen(m_writeStream);
}
File_Output::~File_Output()
{
CFWriteStreamClose(m_writeStream);
CFRelease(m_writeStream);
}
CFIndex File_Output::write(const UInt8 *buffer, CFIndex bufferLength)
{
return CFWriteStreamWrite(m_writeStream, buffer, bufferLength);
}
} // namespace astreamer
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_FILE_OUTPUT_H
#define ASTREAMER_FILE_OUTPUT_H
#import <CoreFoundation/CoreFoundation.h>
namespace astreamer {
class File_Output {
private:
File_Output(const File_Output&);
File_Output& operator=(const File_Output&);
CFWriteStreamRef m_writeStream;
public:
File_Output(CFURLRef fileURL);
~File_Output();
CFIndex write(const UInt8 *buffer, CFIndex bufferLength);
};
} // namespace astreamer
#endif // ASTREAMER_FILE_OUTPUT_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_FILE_STREAM_H
#define ASTREAMER_FILE_STREAM_H
#import "input_stream.h"
#import "id3_parser.h"
namespace astreamer {
class File_Stream : public Input_Stream {
private:
File_Stream(const File_Stream&);
File_Stream& operator=(const File_Stream&);
CFURLRef m_url;
CFReadStreamRef m_readStream;
bool m_scheduledInRunLoop;
bool m_readPending;
Input_Stream_Position m_position;
UInt8 *m_fileReadBuffer;
ID3_Parser *m_id3Parser;
CFStringRef m_contentType;
static void readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo);
public:
File_Stream();
virtual ~File_Stream();
Input_Stream_Position position();
CFStringRef contentType();
void setContentType(CFStringRef contentType);
size_t contentLength();
bool open();
bool open(const Input_Stream_Position& position);
void close();
void setScheduledInRunLoop(bool scheduledInRunLoop);
void setUrl(CFURLRef url);
static bool canHandleUrl(CFURLRef url);
/* ID3_Parser_Delegate */
void id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
void id3tagSizeAvailable(UInt32 tagSize);
};
} // namespace astreamer
#endif // ASTREAMER_FILE_STREAM_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_HTTP_STREAM_H
#define ASTREAMER_HTTP_STREAM_H
#import <CFNetwork/CFNetwork.h>
#import <vector>
#import <map>
#import "input_stream.h"
#import "id3_parser.h"
namespace astreamer {
class HTTP_Stream : public Input_Stream {
private:
HTTP_Stream(const HTTP_Stream&);
HTTP_Stream& operator=(const HTTP_Stream&);
static CFStringRef httpRequestMethod;
static CFStringRef httpUserAgentHeader;
static CFStringRef httpRangeHeader;
static CFStringRef icyMetaDataHeader;
static CFStringRef icyMetaDataValue;
CFURLRef m_url;
CFReadStreamRef m_readStream;
bool m_scheduledInRunLoop;
bool m_readPending;
Input_Stream_Position m_position;
/* HTTP headers */
bool m_httpHeadersParsed;
CFStringRef m_contentType;
size_t m_contentLength;
UInt64 m_bytesRead;
/* ICY protocol */
bool m_icyStream;
bool m_icyHeaderCR;
bool m_icyHeadersRead;
bool m_icyHeadersParsed;
CFStringRef m_icyName;
std::vector<CFStringRef> m_icyHeaderLines;
size_t m_icyMetaDataInterval;
size_t m_dataByteReadCount;
size_t m_metaDataBytesRemaining;
std::vector<UInt8> m_icyMetaData;
/* Read buffers */
UInt8 *m_httpReadBuffer;
UInt8 *m_icyReadBuffer;
ID3_Parser *m_id3Parser;
CFReadStreamRef createReadStream(CFURLRef url);
void parseHttpHeadersIfNeeded(const UInt8 *buf, const CFIndex bufSize);
void parseICYStream(const UInt8 *buf, const CFIndex bufSize);
CFStringRef createMetaDataStringWithMostReasonableEncoding(const UInt8 *bytes, const CFIndex numBytes);
static void readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo);
public:
HTTP_Stream();
virtual ~HTTP_Stream();
Input_Stream_Position position();
CFStringRef contentType();
size_t contentLength();
bool open();
bool open(const Input_Stream_Position& position);
void close();
void setScheduledInRunLoop(bool scheduledInRunLoop);
void setUrl(CFURLRef url);
static bool canHandleUrl(CFURLRef url);
/* ID3_Parser_Delegate */
void id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
void id3tagSizeAvailable(UInt32 tagSize);
};
} // namespace astreamer
#endif // ASTREAMER_HTTP_STREAM_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_ID3_PARSER_H
#define ASTREAMER_ID3_PARSER_H
#include <map>
#import <CFNetwork/CFNetwork.h>
namespace astreamer {
class ID3_Parser_Delegate;
class ID3_Parser_Private;
class ID3_Parser {
public:
ID3_Parser();
~ID3_Parser();
void reset();
bool wantData();
void feedData(UInt8 *data, UInt32 numBytes);
ID3_Parser_Delegate *m_delegate;
private:
ID3_Parser_Private *m_private;
};
class ID3_Parser_Delegate {
public:
virtual void id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData) = 0;
virtual void id3tagSizeAvailable(UInt32 tagSize) = 0;
};
} // namespace astreamer
#endif // ASTREAMER_ID3_PARSER_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#include "input_stream.h"
namespace astreamer {
Input_Stream::Input_Stream() : m_delegate(0)
{
}
Input_Stream::~Input_Stream()
{
}
}
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_INPUT_STREAM_H
#define ASTREAMER_INPUT_STREAM_H
#import "id3_parser.h"
namespace astreamer {
class Input_Stream_Delegate;
struct Input_Stream_Position {
UInt64 start;
UInt64 end;
};
class Input_Stream : public ID3_Parser_Delegate {
public:
Input_Stream();
virtual ~Input_Stream();
Input_Stream_Delegate* m_delegate;
virtual Input_Stream_Position position() = 0;
virtual CFStringRef contentType() = 0;
virtual size_t contentLength() = 0;
virtual bool open() = 0;
virtual bool open(const Input_Stream_Position& position) = 0;
virtual void close() = 0;
virtual void setScheduledInRunLoop(bool scheduledInRunLoop) = 0;
virtual void setUrl(CFURLRef url) = 0;
};
class Input_Stream_Delegate {
public:
virtual void streamIsReadyRead() = 0;
virtual void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes) = 0;
virtual void streamEndEncountered() = 0;
virtual void streamErrorOccurred(CFStringRef errorDesc) = 0;
virtual void streamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData) = 0;
virtual void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes) = 0;
};
} // namespace astreamer
#endif // ASTREAMER_INPUT_STREAM_H
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#include "stream_configuration.h"
namespace astreamer {
Stream_Configuration::Stream_Configuration() :
userAgent(NULL),
cacheDirectory(NULL),
predefinedHttpHeaderValues(NULL)
{
}
Stream_Configuration::~Stream_Configuration()
{
if (userAgent) {
CFRelease(userAgent);
userAgent = NULL;
}
}
Stream_Configuration* Stream_Configuration::configuration()
{
static Stream_Configuration config;
return &config;
}
}
/*
* This file is part of the FreeStreamer project,
* (C)Copyright 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
* See the file ''LICENSE'' for using the code.
*
* https://github.com/muhku/FreeStreamer
*/
#ifndef ASTREAMER_STREAM_CONFIGURATION_H
#define ASTREAMER_STREAM_CONFIGURATION_H
#import <CoreFoundation/CoreFoundation.h>
namespace astreamer {
struct Stream_Configuration {
unsigned bufferCount;
unsigned bufferSize;
unsigned maxPacketDescs;
unsigned httpConnectionBufferSize;
double outputSampleRate;
long outputNumChannels;
int bounceInterval;
int maxBounceCount;
int startupWatchdogPeriod;
int maxPrebufferedByteCount;
bool usePrebufferSizeCalculationInSeconds;
bool usePrebufferSizeCalculationInPackets;
int requiredInitialPrebufferedByteCountForContinuousStream;
int requiredInitialPrebufferedByteCountForNonContinuousStream;
int requiredPrebufferSizeInSeconds;
int requiredInitialPrebufferedPacketCount;
CFStringRef userAgent;
CFStringRef cacheDirectory;
CFDictionaryRef predefinedHttpHeaderValues;
bool cacheEnabled;
bool seekingFromCacheEnabled;
bool automaticAudioSessionHandlingEnabled;
bool enableTimeAndPitchConversion;
bool requireStrictContentTypeChecking;
int maxDiskCacheSize;
static Stream_Configuration *configuration();
private:
Stream_Configuration();
~Stream_Configuration();
Stream_Configuration(const Stream_Configuration&);
Stream_Configuration& operator=(const Stream_Configuration&);
};
} // namespace astreamer
#endif // ASTREAMER_STREAM_CONFIGURATION_H
Copyright (c) 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
The FreeStreamer framework bundles Reachability which is licensed under the following
license:
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.
FreeStreamer
====================
A streaming audio player for iOS and OS X.
Features
====================
- **CPU-friendly** design (uses 1% of CPU on average when streaming)
- **Multiple protocols supported**: ShoutCast, standard HTTP, local files
- **Prepared for tough network conditions**: adjustable buffer sizes, stream pre-buffering and restart on failures
- **Metadata support**: ShoutCast metadata, IDv2 tags
- **Local disk caching**: user only needs to stream a file once and after that it can be played from a local cache
- **Preloading**: playback can start immediately without needing to wait for buffering
- **Record**: support recording the stream contents to a file
- **Access the PCM audio samples**: as an example, a visualizer is included
Documentation
====================
See the [FAQ](https://github.com/muhku/FreeStreamer/wiki/FreeStreamer-FAQ) (Frequently Asked Questions) in the wiki. We also have an [API documentation](http://muhku.github.io/api/) available. The [usage instructions](https://github.com/muhku/FreeStreamer/wiki/Using-the-player-in-your-own-project) are also covered in the wiki.
Is somebody using this in real life?
====================
The short answer is yes! Check out our [website](http://muhku.github.io/) for the reference applications.
Reporting bugs and contributing
====================
For code contributions and other questions, it is preferrable to create a Github pull request. I don't have time for private email support, so usually the best way to get help is to interact with Github issues.
License
====================
See [LICENSE.txt](https://github.com/muhku/FreeStreamer/blob/master/LICENSE.txt) for the license.
Donations
====================
It is possible to use [PayPal](http://muhku.github.io/donate.html) for donations.
......@@ -20,10 +20,13 @@ PODS:
- DKNightVersion/UIKit (2.4.3):
- DKNightVersion/Core
- DOUAudioStreamer (0.2.16)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- MJRefresh (3.7.5)
- Reachability (3.2)
- YTKNetwork (3.0.6):
- AFNetworking/NSURLSession (~> 4.0)
- YYCache (1.0.4)
......@@ -40,6 +43,7 @@ PODS:
DEPENDENCIES:
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- FreeStreamer (~> 4.0.0)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MBProgressHUD (~> 1.2.0)
......@@ -54,10 +58,12 @@ SPEC REPOS:
- AFNetworking
- DKNightVersion
- DOUAudioStreamer
- FreeStreamer
- lottie-ios
- Masonry
- MBProgressHUD
- MJRefresh
- Reachability
- YTKNetwork
- YYCache
- YYImage
......@@ -68,16 +74,18 @@ SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
PODFILE CHECKSUM: 5f273d0f03f58db41d7f0a6d3d96a8bd054ab744
PODFILE CHECKSUM: d78d9f7fd55a2a7be3fae24d212bdd5eab78666c
COCOAPODS: 1.11.3
Copyright (c) 2011-2013, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.
[![Reference Status](https://www.versioneye.com/objective-c/reachability/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/reachability/references)
# Reachability
This is a drop-in replacement for Apple's `Reachability` class. It is ARC-compatible, and it uses the new GCD methods to notify of network interface changes.
In addition to the standard `NSNotification`, it supports the use of blocks for when the network becomes reachable and unreachable.
Finally, you can specify whether a WWAN connection is considered "reachable".
*DO NOT OPEN BUGS UNTIL YOU HAVE TESTED ON DEVICE*
## Requirements
Once you have added the `.h/m` files to your project, simply:
* Go to the `Project->TARGETS->Build Phases->Link Binary With Libraries`.
* Press the plus in the lower left of the list.
* Add `SystemConfiguration.framework`.
Boom, you're done.
## Examples
### Block Example
This sample uses blocks to notify when the interface state has changed. The blocks will be called on a **BACKGROUND THREAD**, so you need to dispatch UI updates onto the main thread.
// Allocate a reachability object
Reachability* reach = [Reachability reachabilityWithHostname:@"www.google.com"];
// Set the blocks
reach.reachableBlock = ^(Reachability*reach)
{
// keep in mind this is called on a background thread
// and if you are updating the UI it needs to happen
// on the main thread, like this:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"REACHABLE!");
});
};
reach.unreachableBlock = ^(Reachability*reach)
{
NSLog(@"UNREACHABLE!");
};
// Start the notifier, which will cause the reachability object to retain itself!
[reach startNotifier];
### `NSNotification` Example
This sample will use `NSNotification`s to notify when the interface has changed. They will be delivered on the **MAIN THREAD**, so you *can* do UI updates from within the function.
In addition, it asks the `Reachability` object to consider the WWAN (3G/EDGE/CDMA) as a non-reachable connection (you might use this if you are writing a video streaming app, for example, to save the user's data plan).
// Allocate a reachability object
Reachability* reach = [Reachability reachabilityWithHostname:@"www.google.com"];
// Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA
reach.reachableOnWWAN = NO;
// Here we set up a NSNotification observer. The Reachability that caused the notification
// is passed in the object parameter
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
[reach startNotifier];
## Tell the world
Head over to [Projects using Reachability](https://github.com/tonymillion/Reachability/wiki/Projects-using-Reachability) and add your project for "Maximum Wins!".
/*
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/**
* Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X.
*
* @see http://nshipster.com/ns_enum-ns_options/
**/
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
extern NSString *const kReachabilityChangedNotification;
typedef NS_ENUM(NSInteger, NetworkStatus) {
// Apple NetworkStatus Compatible Names.
NotReachable = 0,
ReachableViaWiFi = 2,
ReachableViaWWAN = 1
};
@class Reachability;
typedef void (^NetworkReachable)(Reachability * reachability);
typedef void (^NetworkUnreachable)(Reachability * reachability);
@interface Reachability : NSObject
@property (nonatomic, copy) NetworkReachable reachableBlock;
@property (nonatomic, copy) NetworkUnreachable unreachableBlock;
@property (nonatomic, assign) BOOL reachableOnWWAN;
+(Reachability*)reachabilityWithHostname:(NSString*)hostname;
// This is identical to the function above, but is here to maintain
//compatibility with Apples original code. (see .m)
+(Reachability*)reachabilityWithHostName:(NSString*)hostname;
+(Reachability*)reachabilityForInternetConnection;
+(Reachability*)reachabilityWithAddress:(void *)hostAddress;
+(Reachability*)reachabilityForLocalWiFi;
-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
-(BOOL)startNotifier;
-(void)stopNotifier;
-(BOOL)isReachable;
-(BOOL)isReachableViaWWAN;
-(BOOL)isReachableViaWiFi;
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired; // Identical DDG variant.
-(BOOL)connectionRequired; // Apple's routine.
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand;
// Is user intervention required?
-(BOOL)isInterventionRequired;
-(NetworkStatus)currentReachabilityStatus;
-(SCNetworkReachabilityFlags)reachabilityFlags;
-(NSString*)currentReachabilityString;
-(NSString*)currentReachabilityFlags;
@end
<?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>4.0.0</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_FreeStreamer : NSObject
@end
@implementation PodsDummy_FreeStreamer
@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 "FSAudioController.h"
#import "FSAudioStream.h"
#import "FSCheckContentTypeRequest.h"
#import "FSParsePlaylistRequest.h"
#import "FSParseRssPodcastFeedRequest.h"
#import "FSPlaylistItem.h"
#import "FSXMLHttpRequest.h"
FOUNDATION_EXPORT double FreeStreamerVersionNumber;
FOUNDATION_EXPORT const unsigned char FreeStreamerVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Reachability"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/include/libxml2
OTHER_LDFLAGS = $(inherited) -l"c++" -l"xml2" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "MediaPlayer" -framework "Reachability" -framework "SystemConfiguration"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/FreeStreamer
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 FreeStreamer {
umbrella header "FreeStreamer-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Reachability"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/include/libxml2
OTHER_LDFLAGS = $(inherited) -l"c++" -l"xml2" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "MediaPlayer" -framework "Reachability" -framework "SystemConfiguration"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/FreeStreamer
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
......@@ -84,6 +84,63 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## FreeStreamer
Copyright (c) 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
The FreeStreamer framework bundles Reachability which is licensed under the following
license:
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.
## MBProgressHUD
Copyright © 2009-2020 Matej Bukovinski
......@@ -151,6 +208,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## Reachability
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.
## YTKNetwork
Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
......
......@@ -115,6 +115,69 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011-2018 Matias Muhonen &lt;mmu@iki.fi&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:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
The FreeStreamer framework bundles Reachability which is licensed under the following
license:
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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>FreeStreamer</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright © 2009-2020 Matej Bukovinski
Permission is hereby granted, free of charge, to any person obtaining a copy
......@@ -200,6 +263,26 @@ THE SOFTWARE.</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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>Reachability</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
Permission is hereby granted, free of charge, to any person obtaining a copy
......
......@@ -2,9 +2,11 @@ ${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}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.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}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
......
......@@ -2,9 +2,11 @@ ${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}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.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}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
......
......@@ -2,9 +2,11 @@ ${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}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.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}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
......
......@@ -179,9 +179,11 @@ 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}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework"
......@@ -193,9 +195,11 @@ 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}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework"
......@@ -207,9 +211,11 @@ 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}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.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}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
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}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.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}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.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}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"sqlite3" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
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}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
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}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.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}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.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}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"sqlite3" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
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}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
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}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.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}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.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}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"sqlite3" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
<?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>3.2.0</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_Reachability : NSObject
@end
@implementation PodsDummy_Reachability
@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 "Reachability.h"
FOUNDATION_EXPORT double ReachabilityVersionNumber;
FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Reachability
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "SystemConfiguration"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Reachability
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 Reachability {
umbrella header "Reachability-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Reachability
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "SystemConfiguration"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Reachability
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
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!