B面1.0.5再优化版本,处理启动页加载时间过长,无法迅速确定前往A/B面结果/首页资源加载时间过长

This commit is contained in:
Mr.zhou 2024-06-07 18:52:24 +08:00
parent 716180557b
commit 63078adc81
89 changed files with 2786 additions and 15543 deletions

View File

@ -23,10 +23,9 @@ pod 'JXSegmentedView'
pod 'JXPagingView/Paging'
#刷新支持
pod 'MJRefresh'
#流音频播放
pod 'FreeStreamer'
#下载按钮
pod "DownloadButton"
#下载框架
pod 'Tiercel'
end

View File

@ -1,14 +1,11 @@
PODS:
- Alamofire (5.9.1)
- DownloadButton (0.1.0)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- IQKeyboardManagerSwift (6.5.16)
- JXPagingView/Paging (2.1.3)
- JXSegmentedView (1.3.3)
- Kingfisher (7.11.0)
- MJRefresh (3.7.9)
- Reachability (3.7.6)
- SnapKit (5.7.1)
- SVProgressHUD (2.3.1):
- SVProgressHUD/Core (= 2.3.1)
@ -19,7 +16,6 @@ PODS:
DEPENDENCIES:
- Alamofire
- DownloadButton
- FreeStreamer
- IQKeyboardManagerSwift
- JXPagingView/Paging
- JXSegmentedView
@ -34,13 +30,11 @@ SPEC REPOS:
trunk:
- Alamofire
- DownloadButton
- FreeStreamer
- IQKeyboardManagerSwift
- JXPagingView
- JXSegmentedView
- Kingfisher
- MJRefresh
- Reachability
- SnapKit
- SVProgressHUD
- SwiftDate
@ -49,18 +43,16 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
DownloadButton: 49a21a89e0d7d1b42d9134f79aaa40e727cd57c3
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68
Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90
PODFILE CHECKSUM: 2aaf13c9ed7d2b78bcf21030bbbacf03b3a0212c
PODFILE CHECKSUM: 436104abc66aacc2c16f90adefe5230845e81453
COCOAPODS: 1.15.2

View File

@ -1,274 +0,0 @@
/*
* 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

View File

@ -1,875 +0,0 @@
/*
* 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 "FSAudioController.h"
#import "FSPlaylistItem.h"
#import "FSCheckContentTypeRequest.h"
#import "FSParsePlaylistRequest.h"
#import "FSParseRssPodcastFeedRequest.h"
#import <AVFoundation/AVFoundation.h>
/**
* Private interface for FSAudioController.
*/
@interface FSAudioController ()
- (void)notifyRetrievingURL;
@property (readonly) FSAudioStream *audioStream;
@property (readonly) FSCheckContentTypeRequest *checkContentTypeRequest;
@property (readonly) FSParsePlaylistRequest *parsePlaylistRequest;
@property (readonly) FSParseRssPodcastFeedRequest *parseRssPodcastFeedRequest;
@property (nonatomic,assign) BOOL readyToPlay;
@property (nonatomic,assign) NSUInteger currentPlaylistItemIndex;
@property (nonatomic,strong) NSMutableArray *playlistItems;
@property (nonatomic,strong) NSMutableArray *streams;
@property (nonatomic,assign) BOOL needToSetVolume;
@property (nonatomic,assign) BOOL songSwitchInProgress;
@property (nonatomic,assign) float outputVolume;
- (void)audioStreamStateDidChange:(NSNotification *)notification;
- (void)deactivateInactivateStreams:(NSUInteger)currentActiveStream;
- (void)setAudioSessionActive:(BOOL)active;
@end
/**
* Acts as a proxy object for FSAudioStream. Lazily initializes
* the stream when it is needed.
*
* A call to deactivate releases the stream.
*/
@interface FSAudioStreamProxy : NSObject {
FSAudioStream *_audioStream;
}
@property (readonly) FSAudioStream *audioStream;
@property (nonatomic,copy) NSURL *url;
@property (nonatomic,weak) FSAudioController *audioController;
- (void)deactivate;
@end
/*
* =======================================
* FSAudioStreamProxy implementation.
* =======================================
*/
@implementation FSAudioStreamProxy
- (id)init
{
if (self = [super init]) {
}
return self;
}
- (id)initWithAudioController:(FSAudioController *)controller
{
if (self = [self init]) {
self.audioController = controller;
}
return self;
}
- (void)dealloc
{
if (self.audioController.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] FSAudioStreamProxy.dealloc: %@", __LINE__, self.url);
}
[self deactivate];
}
- (FSAudioStream *)audioStream
{
if (!_audioStream) {
FSStreamConfiguration *conf;
if (self.audioController.configuration) {
conf = self.audioController.configuration;
} else {
conf = [[FSStreamConfiguration alloc] init];
}
// Disable audio session handling for the audio stream; audio controller handles it
conf.automaticAudioSessionHandlingEnabled = NO;
_audioStream = [[FSAudioStream alloc] initWithConfiguration:conf];
if (self.audioController.needToSetVolume) {
_audioStream.volume = self.audioController.outputVolume;
}
if (self.url) {
_audioStream.url = self.url;
}
}
return _audioStream;
}
- (void)deactivate
{
[_audioStream stop];
_audioStream = nil;
}
@end
/*
* =======================================
* FSAudioController implementation
* =======================================
*/
@implementation FSAudioController
-(id)init
{
if (self = [super init]) {
_url = nil;
_checkContentTypeRequest = nil;
_parsePlaylistRequest = nil;
_readyToPlay = NO;
_playlistItems = [[NSMutableArray alloc] init];
_streams = [[NSMutableArray alloc] init];
self.preloadNextPlaylistItemAutomatically = YES;
self.enableDebugOutput = NO;
self.automaticAudioSessionHandlingEnabled = YES;
self.configuration = [[FSStreamConfiguration alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioStreamStateDidChange:)
name:FSAudioStreamStateChangeNotification
object:nil];
}
return self;
}
- (id)initWithUrl:(NSURL *)url
{
if (self = [self init]) {
self.url = url;
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_checkContentTypeRequest cancel];
[_parsePlaylistRequest cancel];
[_parseRssPodcastFeedRequest cancel];
for (FSAudioStreamProxy *proxy in _streams) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] dealloc. Deactivating stream %@", __LINE__, proxy.url);
}
[proxy deactivate];
}
[self setAudioSessionActive:NO];
}
- (void)audioStreamStateDidChange:(NSNotification *)notification
{
if (notification.object == self) {
// URL retrieving notification from ourselves, ignore
return;
}
if (!(notification.object == self.audioStream)) {
// This doesn't concern us, return
return;
}
NSDictionary *dict = [notification userInfo];
int state = [[dict valueForKey:FSAudioStreamNotificationKey_State] intValue];
if (state == kFSAudioStreamEndOfFile) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] EOF reached for %@", __LINE__, self.audioStream.url);
}
if (!self.preloadNextPlaylistItemAutomatically) {
// No preloading wanted, skip
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] Preloading disabled, return.", __LINE__);
}
return;
}
// Reached EOF for this stream, do we have another item waiting in the playlist?
if ([self hasNextItem]) {
FSAudioStreamProxy *proxy = [_streams objectAtIndex:self.currentPlaylistItemIndex + 1];
FSAudioStream *nextStream = proxy.audioStream;
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] Preloading %@", __LINE__, nextStream.url);
}
if ([self.delegate respondsToSelector:@selector(audioController:allowPreloadingForStream:)]) {
if ([self.delegate audioController:self allowPreloadingForStream:nextStream]) {
[nextStream preload];
} else {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] Preloading disallowed for stream %@", __LINE__, nextStream.url);
}
}
} else {
// Start preloading the next stream; we can load this as there is no override
[nextStream preload];
}
if ([self.delegate respondsToSelector:@selector(audioController:preloadStartedForStream:)]) {
[self.delegate audioController:self preloadStartedForStream:nextStream];
}
}
} else if (state == kFsAudioStreamStopped && !self.songSwitchInProgress) {
if (self.enableDebugOutput) {
NSLog(@"Stream %@ stopped. No next playlist items. Deactivating audio session", self.audioStream.url);
}
[self setAudioSessionActive:NO];
} else if (state == kFsAudioStreamPlaybackCompleted && [self hasNextItem]) {
self.currentPlaylistItemIndex = self.currentPlaylistItemIndex + 1;
self.songSwitchInProgress = YES;
[self play];
} else if (state == kFsAudioStreamFailed) {
if (self.enableDebugOutput) {
NSLog(@"Stream %@ failed. Deactivating audio session", self.audioStream.url);
}
[self setAudioSessionActive:NO];
} else if (state == kFsAudioStreamBuffering) {
if (self.enableDebugOutput) {
NSLog(@"Stream buffering. Activating audio session");
}
self.songSwitchInProgress = NO;
if (self.automaticAudioSessionHandlingEnabled) {
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
#endif
}
[self setAudioSessionActive:YES];
} else if (state == kFsAudioStreamPlaying) {
self.currentPlaylistItem.audioDataByteCount = self.activeStream.audioDataByteCount;
}
}
- (void)deactivateInactivateStreams:(NSUInteger)currentActiveStream
{
NSUInteger streamIndex = 0;
for (FSAudioStreamProxy *proxy in _streams) {
if (streamIndex != currentActiveStream) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] Deactivating stream %@", __LINE__, proxy.url);
}
[proxy deactivate];
}
streamIndex++;
}
}
- (void)setAudioSessionActive:(BOOL)active
{
if (self.automaticAudioSessionHandlingEnabled) {
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
[[AVAudioSession sharedInstance] setActive:active withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
#else
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
[[AVAudioSession sharedInstance] setActive:active error:nil];
#endif
#endif
}
}
/*
* =======================================
* Properties
* =======================================
*/
- (FSAudioStream *)audioStream
{
FSAudioStream *stream = nil;
if ([_streams count] == 0) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] Stream count %lu, creating a proxy object", __LINE__, (unsigned long)[_streams count]);
}
FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self];
[_streams addObject:proxy];
}
FSAudioStreamProxy *proxy = [_streams objectAtIndex:self.currentPlaylistItemIndex];
stream = proxy.audioStream;
return stream;
}
- (FSCheckContentTypeRequest *)checkContentTypeRequest
{
if (!_checkContentTypeRequest) {
__weak FSAudioController *weakSelf = self;
_checkContentTypeRequest = [[FSCheckContentTypeRequest alloc] init];
_checkContentTypeRequest.url = self.url;
_checkContentTypeRequest.onCompletion = ^() {
if (weakSelf.checkContentTypeRequest.playlist) {
// The URL is a playlist; retrieve the contents
[weakSelf.parsePlaylistRequest start];
} else if (weakSelf.checkContentTypeRequest.xml) {
// The URL may be an RSS feed, check the contents
[weakSelf.parseRssPodcastFeedRequest start];
} else {
// Not a playlist; try directly playing the URL
weakSelf.readyToPlay = YES;
[weakSelf play];
}
};
_checkContentTypeRequest.onFailure = ^() {
// Failed to check the format; try playing anyway
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSAudioController: Failed to check the format, trying to play anyway, URL: %@", weakSelf.audioStream.url);
#endif
weakSelf.readyToPlay = YES;
[weakSelf play];
};
}
return _checkContentTypeRequest;
}
- (FSParsePlaylistRequest *)parsePlaylistRequest
{
if (!_parsePlaylistRequest) {
__weak FSAudioController *weakSelf = self;
_parsePlaylistRequest = [[FSParsePlaylistRequest alloc] init];
_parsePlaylistRequest.onCompletion = ^() {
[weakSelf playFromPlaylist:weakSelf.parsePlaylistRequest.playlistItems];
};
_parsePlaylistRequest.onFailure = ^() {
// Failed to parse the playlist; try playing anyway
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSAudioController: Playlist parsing failed, trying to play anyway, URL: %@", weakSelf.audioStream.url);
#endif
weakSelf.readyToPlay = YES;
[weakSelf play];
};
}
return _parsePlaylistRequest;
}
- (FSParseRssPodcastFeedRequest *)parseRssPodcastFeedRequest
{
if (!_parseRssPodcastFeedRequest) {
__weak FSAudioController *weakSelf = self;
_parseRssPodcastFeedRequest = [[FSParseRssPodcastFeedRequest alloc] init];
_parseRssPodcastFeedRequest.onCompletion = ^() {
[weakSelf playFromPlaylist:weakSelf.parseRssPodcastFeedRequest.playlistItems];
};
_parseRssPodcastFeedRequest.onFailure = ^() {
// Failed to parse the XML file; try playing anyway
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
NSLog(@"FSAudioController: Failed to parse the RSS feed, trying to play anyway, URL: %@", weakSelf.audioStream.url);
#endif
weakSelf.readyToPlay = YES;
[weakSelf play];
};
}
return _parseRssPodcastFeedRequest;
}
- (void)notifyRetrievingURL
{
if (self.onStateChange) {
self.onStateChange(kFsAudioStreamRetrievingURL);
}
}
- (BOOL)isPlaying
{
return [self.audioStream isPlaying];
}
/*
* =======================================
* Public interface
* =======================================
*/
- (void)play
{
if (!self.readyToPlay) {
/*
* Not ready to play; start by checking the content type of the given
* URL.
*/
[self.checkContentTypeRequest start];
NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: @(kFsAudioStreamRetrievingURL)};
NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:self userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotification:notification];
[NSTimer scheduledTimerWithTimeInterval:0
target:self
selector:@selector(notifyRetrievingURL)
userInfo:nil
repeats:NO];
return;
}
if ([self.playlistItems count] > 0) {
if (self.currentPlaylistItem.originatingUrl) {
self.audioStream.url = self.currentPlaylistItem.originatingUrl;
} else {
self.audioStream.url = self.currentPlaylistItem.url;
}
} else {
self.audioStream.url = self.url;
}
if (self.onStateChange) {
self.audioStream.onStateChange = self.onStateChange;
}
if (self.onMetaDataAvailable) {
self.audioStream.onMetaDataAvailable = self.onMetaDataAvailable;
}
if (self.onFailure) {
self.audioStream.onFailure = self.onFailure;
}
FSAudioStream *stream = self.audioStream;
if (self.enableDebugOutput) {
NSLog(@"Playing %@", stream);
}
[stream play];
}
- (void)playFromURL:(NSURL*)url
{
if (!url) {
return;
}
[_playlistItems removeAllObjects];
[self stop];
self.url = url;
[self play];
}
- (void)playFromPlaylist:(NSArray *)playlist
{
[self playFromPlaylist:playlist itemIndex:0];
}
- (void)playFromPlaylist:(NSArray *)playlist itemIndex:(NSUInteger)index
{
[self stop];
self.playlistItems = [[NSMutableArray alloc] init];
_streams = [[NSMutableArray alloc] init];
self.currentPlaylistItemIndex = 0;
[self.playlistItems addObjectsFromArray:playlist];
for (FSPlaylistItem *item in playlist) {
FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self];
proxy.url = item.url;
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] playFromPlaylist. Adding stream proxy for %@", __LINE__, proxy.url);
}
[_streams addObject:proxy];
}
[self playItemAtIndex:index];
}
- (void)playItemAtIndex:(NSUInteger)index
{
NSUInteger count = [self countOfItems];
if (count == 0) {
return;
}
if (index >= count) {
return;
}
[self.audioStream stop];
self.currentPlaylistItemIndex = index;
self.readyToPlay = YES;
[self deactivateInactivateStreams:index];
[self play];
}
- (NSUInteger)countOfItems
{
return [self.playlistItems count];
}
- (void)addItem:(FSPlaylistItem *)item
{
if (!item) {
return;
}
[self.playlistItems addObject:item];
FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self];
proxy.url = item.url;
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] addItem. Adding stream proxy for %@", __LINE__, proxy.url);
}
[_streams addObject:proxy];
}
- (void)insertItem:(FSPlaylistItem *)item atIndex:(NSInteger)index
{
if (!item) {
return;
}
if (index > self.playlistItems.count) {
return;
}
if(self.playlistItems.count == 0 && index == 0) {
[self addItem:item];
return;
}
[self.playlistItems insertObject:item
atIndex:index];
FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self];
proxy.url = item.url;
[_streams insertObject:proxy
atIndex:index];
if(index <= self.currentPlaylistItemIndex) {
_currentPlaylistItemIndex++;
}
}
- (void)replaceItemAtIndex:(NSUInteger)index withItem:(FSPlaylistItem *)item
{
NSUInteger count = [self countOfItems];
if (count == 0) {
return;
}
if (index >= count) {
return;
}
if (self.currentPlaylistItemIndex == index) {
// If the item is currently playing, do not allow the replacement
return;
}
[self.playlistItems replaceObjectAtIndex:index withObject:item];
FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self];
proxy.url = item.url;
[_streams replaceObjectAtIndex:index withObject:proxy];
}
- (void)moveItemAtIndex:(NSUInteger)from toIndex:(NSUInteger)to {
NSUInteger count = [self countOfItems];
if (count == 0) {
return;
}
if (from >= count || to >= count) {
return;
}
if(from == self.currentPlaylistItemIndex) {
_currentPlaylistItemIndex = to;
}
else if(from < self.currentPlaylistItemIndex && to > self.currentPlaylistItemIndex) {
_currentPlaylistItemIndex--;
}
else if(from > self.currentPlaylistItemIndex && to <= self.currentPlaylistItemIndex) {
_currentPlaylistItemIndex++;
}
id object = [self.playlistItems objectAtIndex:from];
[self.playlistItems removeObjectAtIndex:from];
[self.playlistItems insertObject:object atIndex:to];
id obj = [_streams objectAtIndex:from];
[_streams removeObjectAtIndex:from];
[_streams insertObject:obj atIndex:to];
}
- (void)removeItemAtIndex:(NSUInteger)index
{
NSUInteger count = [self countOfItems];
if (count == 0) {
return;
}
if (index >= count) {
return;
}
if (self.currentPlaylistItemIndex == index && self.isPlaying) {
// If the item is currently playing, do not allow the removal
return;
}
FSPlaylistItem *current = self.currentPlaylistItem;
[self.playlistItems removeObjectAtIndex:index];
if (self.enableDebugOutput) {
FSAudioStreamProxy *proxy = [_streams objectAtIndex:index];
NSLog(@"[FSAudioController.m:%i] removeItemAtIndex. Removing stream proxy %@", __LINE__, proxy.url);
}
[_streams removeObjectAtIndex:index];
// Update the current playlist item to be correct after the removal
NSUInteger itemIndex = 0;
for (FSPlaylistItem *item in self.playlistItems) {
if (item == current) {
self.currentPlaylistItemIndex = itemIndex;
break;
}
itemIndex++;
}
}
- (void)stop
{
if ([_streams count] > 0) {
// Avoid creating an instance if we don't have it
[self.audioStream stop];
}
[_checkContentTypeRequest cancel];
[_parsePlaylistRequest cancel];
[_parseRssPodcastFeedRequest cancel];
self.readyToPlay = NO;
}
- (void)pause
{
[self.audioStream pause];
}
-(BOOL)hasMultiplePlaylistItems
{
return ([self.playlistItems count] > 1);
}
-(BOOL)hasNextItem
{
return [self hasMultiplePlaylistItems] && (self.currentPlaylistItemIndex + 1 < [self.playlistItems count]);
}
-(BOOL)hasPreviousItem
{
return ([self hasMultiplePlaylistItems] && (self.currentPlaylistItemIndex != 0));
}
-(void)playNextItem
{
if ([self hasNextItem]) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] playNexItem. Stopping stream %@", __LINE__, self.audioStream.url);
}
[self.audioStream stop];
[self deactivateInactivateStreams:self.currentPlaylistItemIndex];
self.currentPlaylistItemIndex = self.currentPlaylistItemIndex + 1;
[self play];
}
}
-(void)playPreviousItem
{
if ([self hasPreviousItem]) {
if (self.enableDebugOutput) {
NSLog(@"[FSAudioController.m:%i] playPreviousItem. Stopping stream %@", __LINE__, self.audioStream.url);
}
[self.audioStream stop];
[self deactivateInactivateStreams:self.currentPlaylistItemIndex];
self.currentPlaylistItemIndex = self.currentPlaylistItemIndex - 1;
[self play];
}
}
/*
* =======================================
* Properties
* =======================================
*/
- (void)setVolume:(float)volume
{
self.outputVolume = volume;
self.needToSetVolume = YES;
if ([_streams count] > 0) {
self.audioStream.volume = self.outputVolume;
}
}
- (float)volume
{
return self.outputVolume;
}
- (void)setUrl:(NSURL *)url
{
[self stop];
if (url) {
NSURL *copyOfURL = [url copy];
_url = copyOfURL;
self.checkContentTypeRequest.url = _url;
self.parsePlaylistRequest.url = _url;
self.parseRssPodcastFeedRequest.url = _url;
if ([_url isFileURL]) {
/*
* Local file URLs can be directly played
*/
self.readyToPlay = YES;
}
} else {
_url = nil;
}
}
- (NSURL* )url
{
if (!_url) {
return nil;
}
NSURL *copyOfURL = [_url copy];
return copyOfURL;
}
- (FSAudioStream *)activeStream
{
if ([_streams count] > 0) {
return self.audioStream;
}
return nil;
}
- (FSPlaylistItem *)currentPlaylistItem
{
if (self.readyToPlay) {
if ([self.playlistItems count] > 0) {
FSPlaylistItem *playlistItem = (self.playlistItems)[self.currentPlaylistItemIndex];
return playlistItem;
}
}
return nil;
}
- (void (^)(FSAudioStreamState state))onStateChange
{
return _onStateChangeBlock;
}
- (void (^)(NSDictionary *metaData))onMetaDataAvailable
{
return _onMetaDataAvailableBlock;
}
- (void (^)(FSAudioStreamError error, NSString *errorDescription))onFailure
{
return _onFailureBlock;
}
- (void)setOnStateChange:(void (^)(FSAudioStreamState))newOnStateValue
{
_onStateChangeBlock = newOnStateValue;
if ([_streams count] > 0) {
self.audioStream.onStateChange = _onStateChangeBlock;
}
}
- (void)setOnMetaDataAvailable:(void (^)(NSDictionary *))newOnMetaDataAvailableValue
{
_onMetaDataAvailableBlock = newOnMetaDataAvailableValue;
if ([_streams count] > 0) {
self.audioStream.onMetaDataAvailable = _onMetaDataAvailableBlock;
}
}
- (void)setOnFailure:(void (^)(FSAudioStreamError error, NSString *errorDescription))newOnFailureValue
{
_onFailureBlock = newOnFailureValue;
if ([_streams count] > 0) {
self.audioStream.onFailure = _onFailureBlock;
}
}
@end

View File

@ -1,600 +0,0 @@
/*
* 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>
#import <CoreAudio/CoreAudioTypes.h>
/**
* The major version of the current release.
*/
#define FREESTREAMER_VERSION_MAJOR 4
/**
* The minor version of the current release.
*/
#define FREESTREAMER_VERSION_MINOR 0
/**
* The reversion of the current release
*/
#define FREESTREAMER_VERSION_REVISION 0
/**
* Follow this notification for the audio stream state changes.
*/
extern NSString* const FSAudioStreamStateChangeNotification;
extern NSString* const FSAudioStreamNotificationKey_State;
/**
* Follow this notification for the audio stream errors.
*/
extern NSString* const FSAudioStreamErrorNotification;
extern NSString* const FSAudioStreamNotificationKey_Error;
/**
* Follow this notification for the audio stream metadata.
*/
extern NSString* const FSAudioStreamMetaDataNotification;
extern NSString* const FSAudioStreamNotificationKey_MetaData;
/**
* The audio stream state.
*/
typedef NS_ENUM(NSInteger, FSAudioStreamState) {
/**
* Retrieving URL.
*/
kFsAudioStreamRetrievingURL,
/**
* Stopped.
*/
kFsAudioStreamStopped,
/**
* Buffering.
*/
kFsAudioStreamBuffering,
/**
* Playing.
*/
kFsAudioStreamPlaying,
/**
* Paused.
*/
kFsAudioStreamPaused,
/**
* Seeking.
*/
kFsAudioStreamSeeking,
/**
* The stream has received all the data for a file.
*/
kFSAudioStreamEndOfFile,
/**
* Failed.
*/
kFsAudioStreamFailed,
/**
* Started retrying.
*/
kFsAudioStreamRetryingStarted,
/**
* Retrying succeeded.
*/
kFsAudioStreamRetryingSucceeded,
/**
* Retrying failed.
*/
kFsAudioStreamRetryingFailed,
/**
* Playback completed.
*/
kFsAudioStreamPlaybackCompleted,
/**
* Unknown state.
*/
kFsAudioStreamUnknownState
};
/**
* The audio stream errors.
*/
typedef NS_ENUM(NSInteger, FSAudioStreamError) {
/**
* No error.
*/
kFsAudioStreamErrorNone = 0,
/**
* Error opening the stream.
*/
kFsAudioStreamErrorOpen = 1,
/**
* Error parsing the stream.
*/
kFsAudioStreamErrorStreamParse = 2,
/**
* Network error.
*/
kFsAudioStreamErrorNetwork = 3,
/**
* Unsupported format.
*/
kFsAudioStreamErrorUnsupportedFormat = 4,
/**
* Stream buffered too often.
*/
kFsAudioStreamErrorStreamBouncing = 5,
/**
* Stream playback was terminated by the operating system.
*/
kFsAudioStreamErrorTerminated = 6
};
@protocol FSPCMAudioStreamDelegate;
@class FSAudioStreamPrivate;
/**
* The audio stream playback position.
*/
typedef struct {
unsigned minute;
unsigned second;
/**
* Playback time in seconds.
*/
float playbackTimeInSeconds;
/**
* Position within the stream, where 0 is the beginning
* and 1.0 is the end.
*/
float position;
} FSStreamPosition;
/**
* The audio stream seek byte offset.
*/
typedef struct {
UInt64 start;
UInt64 end;
/**
* Position within the stream, where 0 is the beginning
* and 1.0 is the end.
*/
float position;
} FSSeekByteOffset;
/**
* Audio levels.
*/
typedef struct {
Float32 averagePower;
Float32 peakPower;
} FSLevelMeterState;
/**
* The low-level stream configuration.
*/
@interface FSStreamConfiguration : NSObject {
}
/**
* The number of buffers.
*/
@property (nonatomic,assign) unsigned bufferCount;
/**
* The size of each buffer.
*/
@property (nonatomic,assign) unsigned bufferSize;
/**
* The number of packet descriptions.
*/
@property (nonatomic,assign) unsigned maxPacketDescs;
/**
* The HTTP connection buffer size.
*/
@property (nonatomic,assign) unsigned httpConnectionBufferSize;
/**
* The output sample rate.
*/
@property (nonatomic,assign) double outputSampleRate;
/**
* The number of output channels.
*/
@property (nonatomic,assign) long outputNumChannels;
/**
* The interval within the stream may enter to the buffering state before it fails.
*/
@property (nonatomic,assign) int bounceInterval;
/**
* The number of times the stream may enter the buffering state before it fails.
*/
@property (nonatomic,assign) int maxBounceCount;
/**
* The stream must start within this seconds before it fails.
*/
@property (nonatomic,assign) int startupWatchdogPeriod;
/**
* Allow buffering of this many bytes before the cache is full.
*/
@property (nonatomic,assign) int maxPrebufferedByteCount;
/**
* Calculate prebuffer sizes dynamically using the stream bitrate in seconds instead of bytes.
*/
@property (nonatomic,assign) BOOL usePrebufferSizeCalculationInSeconds;
/**
* Calculate prebuffer sizes using the packet counts.
*/
@property (nonatomic,assign) BOOL usePrebufferSizeCalculationInPackets;
/**
* Require buffering of this many bytes before the playback can start for a continuous stream.
*/
@property (nonatomic,assign) float requiredPrebufferSizeInSeconds;
/**
* Require buffering of this many bytes before the playback can start for a continuous stream.
*/
@property (nonatomic,assign) int requiredInitialPrebufferedByteCountForContinuousStream;
/**
* Require buffering of this many bytes before the playback can start a non-continuous stream.
*/
@property (nonatomic,assign) int requiredInitialPrebufferedByteCountForNonContinuousStream;
/**
* Require buffering of this many packets before the playback can start.
*/
@property (nonatomic,assign) int requiredInitialPrebufferedPacketCount;
/**
* The HTTP user agent used for stream operations.
*/
@property (nonatomic,strong) NSString *userAgent;
/**
* The directory used for caching the streamed files.
*/
@property (nonatomic,strong) NSString *cacheDirectory;
/**
* The HTTP headers that are appended to the request when the streaming starts. Notice
* that the headers override any headers previously set by FreeStreamer.
*/
@property (nonatomic,strong) NSDictionary *predefinedHttpHeaderValues;
/**
* The property determining if caching the streams to the disk is enabled.
*/
@property (nonatomic,assign) BOOL cacheEnabled;
/**
* The property determining if seeking from the audio packets stored in cache is enabled.
* The benefit is that seeking is faster in the case the audio packets are already cached in memory.
*/
@property (nonatomic,assign) BOOL seekingFromCacheEnabled;
/**
* The property determining if FreeStreamer should handle audio session automatically.
* Leave it on if you don't want to handle the audio session by yourself.
*/
@property (nonatomic,assign) BOOL automaticAudioSessionHandlingEnabled;
/**
* The property enables time and pitch conversion for the audio queue. Put it on
* if you want to use the play rate setting.
*/
@property (nonatomic,assign) BOOL enableTimeAndPitchConversion;
/**
* Requires the content type given by the server to match an audio content type.
*/
@property (nonatomic,assign) BOOL requireStrictContentTypeChecking;
/**
* The maximum size of the disk cache in bytes.
*/
@property (nonatomic,assign) int maxDiskCacheSize;
@end
/**
* Statistics on the stream state.
*/
@interface FSStreamStatistics : NSObject {
}
/**
* Time when the statistics were gathered.
*/
@property (nonatomic,strong) NSDate *snapshotTime;
/**
* Time in a pretty format.
*/
@property (nonatomic,readonly) NSString *snapshotTimeFormatted;
/**
* Audio stream packet count.
*/
@property (nonatomic,assign) NSUInteger audioStreamPacketCount;
/**
* Audio queue used buffers count.
*/
@property (nonatomic,assign) NSUInteger audioQueueUsedBufferCount;
/**
* Audio stream PCM packet queue count.
*/
@property (nonatomic,assign) NSUInteger audioQueuePCMPacketQueueCount;
@end
NSString* freeStreamerReleaseVersion(void);
/**
* FSAudioStream is a class for streaming audio files from an URL.
* It must be directly fed with an URL, which contains audio. That is,
* playlists or other non-audio formats yield an error.
*
* To start playback, the stream must be either initialized with an URL
* or the playback URL can be set with the url property. The playback
* is started with the play method. It is possible to pause or stop
* the stream with the respective methods.
*
* Non-continuous streams (audio streams with a known duration) can be
* seeked with the seekToPosition method.
*
* Note that FSAudioStream is not designed to be thread-safe! That means
* that using the streamer from multiple threads without syncronization
* could cause problems. It is recommended to keep the streamer in the
* main thread and call the streamer methods only from the main thread
* (consider using performSelectorOnMainThread: if calls from multiple
* threads are needed).
*/
@interface FSAudioStream : NSObject {
FSAudioStreamPrivate *_private;
}
/**
* Initializes the audio stream with an URL.
*
* @param url The URL from which the stream data is retrieved.
*/
- (id)initWithUrl:(NSURL *)url;
/**
* Initializes the stream with a configuration.
*
* @param configuration The stream configuration.
*/
- (id)initWithConfiguration:(FSStreamConfiguration *)configuration;
/**
* Starts preload the stream. If no preload URL is
* defined, an error will occur.
*/
- (void)preload;
/**
* Starts playing the stream. If no playback URL is
* defined, an error will occur.
*/
- (void)play;
/**
* Starts playing the stream from the given URL.
*
* @param url The URL from which the stream data is retrieved.
*/
- (void)playFromURL:(NSURL*)url;
/**
* Starts playing the stream from the given offset.
* The offset can be retrieved from the stream with the
* currentSeekByteOffset property.
*
* @param offset The offset where to start playback from.
*/
- (void)playFromOffset:(FSSeekByteOffset)offset;
/**
* 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;
/**
* Rewinds the stream. Only possible for continuous streams.
*
* @param seconds Seconds to rewind the stream.
*/
- (void)rewind:(unsigned)seconds;
/**
* Seeks the stream to a given position. Requires a non-continuous stream
* (a stream with a known duration).
*
* @param position The stream position to seek to.
*/
- (void)seekToPosition:(FSStreamPosition)position;
/**
* Sets the audio stream playback rate from 0.5 to 2.0.
* Value 1.0 means the normal playback rate. Values below
* 1.0 means a slower playback rate than usual and above
* 1.0 a faster playback rate. Notice that using a faster
* playback rate than 1.0 may mean that you have to increase
* the buffer sizes for the stream still to play.
*
* The play rate has only effect if the stream is playing.
*
* @param playRate The playback rate.
*/
- (void)setPlayRate:(float)playRate;
/**
* Returns the playback status: YES if the stream is playing, NO otherwise.
*/
- (BOOL)isPlaying;
/**
* Cleans all cached data from the persistent storage.
*/
- (void)expungeCache;
/**
* The stream URL.
*/
@property (nonatomic,assign) NSURL *url;
/**
* Determines if strict content type checking is required. If the audio stream
* cannot determine that the stream is actually an audio stream, the stream
* does not play. Disabling strict content type checking bypasses the
* stream content type checks and tries to play the stream regardless
* of the content type information given by the server.
*/
@property (nonatomic,assign) BOOL strictContentTypeChecking;
/**
* Set an output file to store the stream contents to a file.
*/
@property (nonatomic,assign) NSURL *outputFile;
/**
* Sets a default content type for the stream. Used if
* the stream content type is not available.
*/
@property (nonatomic,assign) NSString *defaultContentType;
/**
* The property has the content type of the stream, for instance audio/mpeg.
*/
@property (nonatomic,readonly) NSString *contentType;
/**
* The property has the suggested file extension for the stream based on the stream content type.
*/
@property (nonatomic,readonly) NSString *suggestedFileExtension;
/**
* Sets a default content length for the stream. Used if
* the stream content-length is not available.
*/
@property (nonatomic, assign) UInt64 defaultContentLength;
/**
* The property has the content length of the stream (in bytes). The length is zero if
* the stream is continuous.
*/
@property (nonatomic,readonly) UInt64 contentLength;
/**
* 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.
*/
@property (nonatomic,readonly) UInt64 audioDataByteCount;
/**
* This property has the current playback position, if the stream is non-continuous.
* The current playback position cannot be determined for continuous streams.
*/
@property (nonatomic,readonly) FSStreamPosition currentTimePlayed;
/**
* This property has the duration of the stream, if the stream is non-continuous.
* Continuous streams do not have a duration.
*/
@property (nonatomic,readonly) FSStreamPosition duration;
/**
* This property has the current seek byte offset of the stream, if the stream is non-continuous.
* Continuous streams do not have a seek byte offset.
*/
@property (nonatomic,readonly) FSSeekByteOffset currentSeekByteOffset;
/**
* This property has the bit rate of the stream. The bit rate is initially 0,
* before the stream has processed enough packets to calculate the bit rate.
*/
@property (nonatomic,readonly) float bitRate;
/**
* The property is true if the stream is continuous (no known duration).
*/
@property (nonatomic,readonly) BOOL continuous;
/**
* The property is true if the stream has been cached locally.
*/
@property (nonatomic,readonly) BOOL cached;
/**
* This property has the number of bytes buffered for this stream.
*/
@property (nonatomic,readonly) size_t prebufferedByteCount;
/**
* 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 current size of the disk cache.
*/
@property (nonatomic,readonly) unsigned long long totalCachedObjectsSize;
/**
* The property determines the amount of times the stream has tried to retry the playback
* in case of failure.
*/
@property (nonatomic,readonly) NSUInteger retryCount;
/**
* Holds the maximum amount of playback retries that will be
* performed before entering kFsAudioStreamRetryingFailed state.
* Default is 3.
*/
@property (nonatomic,assign) NSUInteger maxRetryCount;
/**
* The property determines the current audio levels.
*/
@property (nonatomic,readonly) FSLevelMeterState levels;
/**
* This property holds the current statistics for the stream state.
*/
@property (nonatomic,readonly) FSStreamStatistics *statistics;
/**
* Called upon completion of the stream. Note that for continuous
* streams this is never called.
*/
@property (copy) void (^onCompletion)(void);
/**
* 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);
/**
* The property has the low-level stream configuration.
*/
@property (readonly) FSStreamConfiguration *configuration;
/**
* Delegate.
*/
@property (nonatomic,unsafe_unretained) IBOutlet id<FSPCMAudioStreamDelegate> delegate;
@end
/**
* To access the PCM audio data, use this delegate.
*/
@protocol FSPCMAudioStreamDelegate <NSObject>
@optional
/**
* Called when there are PCM audio samples available. Do not do any blocking operations
* when you receive the data. Instead, copy the data and process it so that the
* main event loop doesn't block. Failing to do so may cause glitches to the audio playback.
*
* Notice that the delegate callback may occur from other than the main thread so make
* sure your delegate code is thread safe.
*
* @param audioStream The audio stream the samples are from.
* @param samples The samples as a buffer list.
* @param frames The number of frames.
* @param description Description of the data provided.
*/
- (void)audioStream:(FSAudioStream *)audioStream samplesAvailable:(AudioBufferList *)samples frames:(UInt32)frames description: (AudioStreamPacketDescription)description;
@end

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
/*
* 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

View File

@ -1,236 +0,0 @@
/*
* 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

View File

@ -1,71 +0,0 @@
/*
* 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

View File

@ -1,325 +0,0 @@
/*
* 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

View File

@ -1,28 +0,0 @@
/*
* 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

View File

@ -1,100 +0,0 @@
/*
* 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

View File

@ -1,41 +0,0 @@
/*
* 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

View File

@ -1,25 +0,0 @@
/*
* 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

View File

@ -1,112 +0,0 @@
/*
* 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

View File

@ -1,255 +0,0 @@
/*
* 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

View File

@ -1,566 +0,0 @@
/*
* 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 "audio_queue.h"
#include "stream_configuration.h"
#include <pthread.h>
//#define AQ_DEBUG 1
//#define AQ_DEBUG_LOCKS 1
#if !defined (AQ_DEBUG)
#define AQ_TRACE(...) do {} while (0)
#define AQ_ASSERT(...) do {} while (0)
#else
#include <cassert>
#define AQ_TRACE(...) printf(__VA_ARGS__)
#define AQ_ASSERT(...) assert(__VA_ARGS__)
#endif
#if !defined (AQ_DEBUG_LOCKS)
#define AQ_LOCK_TRACE(...) do {} while (0)
#else
#define AQ_LOCK_TRACE(...) printf(__VA_ARGS__)
#endif
namespace astreamer {
/* public */
Audio_Queue::Audio_Queue()
: m_delegate(0),
m_state(IDLE),
m_outAQ(0),
m_fillBufferIndex(0),
m_bytesFilled(0),
m_packetsFilled(0),
m_buffersUsed(0),
m_audioQueueStarted(false),
m_levelMeteringEnabled(false),
m_lastError(noErr),
m_initialOutputVolume(1.0)
{
Stream_Configuration *config = Stream_Configuration::configuration();
m_audioQueueBuffer = new AudioQueueBufferRef[config->bufferCount];
m_packetDescs = new AudioStreamPacketDescription[config->maxPacketDescs];
m_bufferInUse = new bool[config->bufferCount];
for (size_t i=0; i < config->bufferCount; i++) {
m_bufferInUse[i] = false;
}
if (pthread_mutex_init(&m_mutex, NULL) != 0) {
AQ_TRACE("m_mutex init failed!\n");
}
if (pthread_mutex_init(&m_bufferInUseMutex, NULL) != 0) {
AQ_TRACE("m_bufferInUseMutex init failed!\n");
}
if (pthread_cond_init(&m_bufferFreeCondition, NULL) != 0) {
AQ_TRACE("m_bufferFreeCondition init failed!\n");
}
}
Audio_Queue::~Audio_Queue()
{
stop(true);
cleanup();
delete [] m_audioQueueBuffer;
delete [] m_packetDescs;
delete [] m_bufferInUse;
pthread_mutex_destroy(&m_mutex);
pthread_mutex_destroy(&m_bufferInUseMutex);
pthread_cond_destroy(&m_bufferFreeCondition);
}
bool Audio_Queue::initialized()
{
return (m_outAQ != 0);
}
void Audio_Queue::start()
{
// start the queue if it has not been started already
if (m_audioQueueStarted) {
return;
}
OSStatus err = AudioQueueStart(m_outAQ, NULL);
if (!err) {
m_audioQueueStarted = true;
m_levelMeteringEnabled = false;
m_lastError = noErr;
} else {
AQ_TRACE("%s: AudioQueueStart failed!\n", __PRETTY_FUNCTION__);
m_lastError = err;
}
}
void Audio_Queue::pause()
{
if (m_state == RUNNING) {
if (AudioQueuePause(m_outAQ) != 0) {
AQ_TRACE("%s: AudioQueuePause failed!\n", __PRETTY_FUNCTION__);
}
setState(PAUSED);
} else if (m_state == PAUSED) {
AudioQueueStart(m_outAQ, NULL);
setState(RUNNING);
}
}
void Audio_Queue::stop()
{
stop(true);
}
float Audio_Queue::volume()
{
if (!m_outAQ) {
return 1.0;
}
float vol;
OSStatus err = AudioQueueGetParameter(m_outAQ, kAudioQueueParam_Volume, &vol);
if (!err) {
return vol;
}
return 1.0;
}
void Audio_Queue::setVolume(float volume)
{
if (!m_outAQ) {
return;
}
AudioQueueSetParameter(m_outAQ, kAudioQueueParam_Volume, volume);
}
void Audio_Queue::setPlayRate(float playRate)
{
Stream_Configuration *configuration = Stream_Configuration::configuration();
if (!configuration->enableTimeAndPitchConversion) {
#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR)
printf("*** FreeStreamer notification: Trying to set play rate for audio queue but enableTimeAndPitchConversion is disabled from configuration. Play rate settign will not work.\n");
#endif
return;
}
if (!m_outAQ) {
return;
}
if (playRate < 0.5) {
playRate = 0.5;
}
if (playRate > 2.0) {
playRate = 2.0;
}
AudioQueueSetParameter(m_outAQ, kAudioQueueParam_PlayRate, playRate);
}
void Audio_Queue::stop(bool stopImmediately)
{
if (!m_audioQueueStarted) {
AQ_TRACE("%s: audio queue already stopped, return!\n", __PRETTY_FUNCTION__);
return;
}
m_audioQueueStarted = false;
m_levelMeteringEnabled = false;
pthread_mutex_lock(&m_bufferInUseMutex);
pthread_cond_signal(&m_bufferFreeCondition);
pthread_mutex_unlock(&m_bufferInUseMutex);
AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__);
if (AudioQueueFlush(m_outAQ) != 0) {
AQ_TRACE("%s: AudioQueueFlush failed!\n", __PRETTY_FUNCTION__);
}
if (stopImmediately) {
AudioQueueRemovePropertyListener(m_outAQ,
kAudioQueueProperty_IsRunning,
audioQueueIsRunningCallback,
this);
}
if (AudioQueueStop(m_outAQ, stopImmediately) != 0) {
AQ_TRACE("%s: AudioQueueStop failed!\n", __PRETTY_FUNCTION__);
}
if (stopImmediately) {
setState(IDLE);
}
AQ_TRACE("%s: leave\n", __PRETTY_FUNCTION__);
}
AudioTimeStamp Audio_Queue::currentTime()
{
AudioTimeStamp queueTime;
Boolean discontinuity;
memset(&queueTime, 0, sizeof queueTime);
OSStatus err = AudioQueueGetCurrentTime(m_outAQ, NULL, &queueTime, &discontinuity);
if (err) {
AQ_TRACE("AudioQueueGetCurrentTime failed\n");
}
return queueTime;
}
AudioQueueLevelMeterState Audio_Queue::levels()
{
if (!m_levelMeteringEnabled) {
UInt32 enabledLevelMeter = true;
AudioQueueSetProperty(m_outAQ,
kAudioQueueProperty_EnableLevelMetering,
&enabledLevelMeter,
sizeof(UInt32));
m_levelMeteringEnabled = true;
}
AudioQueueLevelMeterState levelMeter;
UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
AudioQueueGetProperty(m_outAQ, kAudioQueueProperty_CurrentLevelMeterDB, &levelMeter, &levelMeterSize);
return levelMeter;
}
void Audio_Queue::init()
{
OSStatus err = noErr;
cleanup();
// create the audio queue
err = AudioQueueNewOutput(&m_streamDesc, audioQueueOutputCallback, this, CFRunLoopGetCurrent(), NULL, 0, &m_outAQ);
if (err) {
AQ_TRACE("%s: error in AudioQueueNewOutput\n", __PRETTY_FUNCTION__);
m_lastError = err;
if (m_delegate) {
m_delegate->audioQueueInitializationFailed();
}
return;
}
Stream_Configuration *configuration = Stream_Configuration::configuration();
// allocate audio queue buffers
for (unsigned int i = 0; i < configuration->bufferCount; ++i) {
err = AudioQueueAllocateBuffer(m_outAQ, configuration->bufferSize, &m_audioQueueBuffer[i]);
if (err) {
/* If allocating the buffers failed, everything else will fail, too.
* Dispose the queue so that we can later on detect that this
* queue in fact has not been initialized.
*/
AQ_TRACE("%s: error in AudioQueueAllocateBuffer\n", __PRETTY_FUNCTION__);
(void)AudioQueueDispose(m_outAQ, true);
m_outAQ = 0;
m_lastError = err;
if (m_delegate) {
m_delegate->audioQueueInitializationFailed();
}
return;
}
}
// listen for kAudioQueueProperty_IsRunning
err = AudioQueueAddPropertyListener(m_outAQ, kAudioQueueProperty_IsRunning, audioQueueIsRunningCallback, this);
if (err) {
AQ_TRACE("%s: error in AudioQueueAddPropertyListener\n", __PRETTY_FUNCTION__);
m_lastError = err;
return;
}
if (configuration->enableTimeAndPitchConversion) {
UInt32 enableTimePitchConversion = 1;
err = AudioQueueSetProperty (m_outAQ, kAudioQueueProperty_EnableTimePitch, &enableTimePitchConversion, sizeof(enableTimePitchConversion));
if (err != noErr) {
AQ_TRACE("Failed to enable time and pitch conversion. Play rate setting will fail\n");
}
}
if (m_initialOutputVolume != 1.0) {
setVolume(m_initialOutputVolume);
}
}
void Audio_Queue::handleAudioPackets(UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions)
{
if (!initialized()) {
AQ_TRACE("%s: warning: attempt to handle audio packets with uninitialized audio queue. return.\n", __PRETTY_FUNCTION__);
return;
}
// this is called by audio file stream when it finds packets of audio
AQ_TRACE("got data. bytes: %u packets: %u\n", inNumberBytes, (unsigned int)inNumberPackets);
/* Place each packet into a buffer and then send each buffer into the audio
queue */
UInt32 i;
for (i = 0; i < inNumberPackets; i++) {
AudioStreamPacketDescription *desc = &inPacketDescriptions[i];
const void *data = (const char*)inInputData + desc->mStartOffset;
if (!initialized()) {
AQ_TRACE("%s: warning: attempt to handle audio packets with uninitialized audio queue. return.\n", __PRETTY_FUNCTION__);
return;
}
Stream_Configuration *config = Stream_Configuration::configuration();
AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__);
UInt32 packetSize = desc->mDataByteSize;
/* This shouldn't happen because most of the time we read the packet buffer
size from the file stream, but if we restored to guessing it we could
come up too small here */
if (packetSize > config->bufferSize) {
AQ_TRACE("%s: packetSize %u > AQ_BUFSIZ %li\n", __PRETTY_FUNCTION__, (unsigned int)packetSize, config->bufferSize);
return;
}
// if the space remaining in the buffer is not enough for this packet, then
// enqueue the buffer and wait for another to become available.
if (config->bufferSize - m_bytesFilled < packetSize) {
enqueueBuffer();
if (!m_audioQueueStarted) {
return;
}
} else {
AQ_TRACE("%s: skipped enqueueBuffer AQ_BUFSIZ - m_bytesFilled %lu, packetSize %u\n", __PRETTY_FUNCTION__, (config->bufferSize - m_bytesFilled), (unsigned int)packetSize);
}
// copy data to the audio queue buffer
AudioQueueBufferRef buf = m_audioQueueBuffer[m_fillBufferIndex];
memcpy((char*)buf->mAudioData, data, packetSize);
// fill out packet description to pass to enqueue() later on
m_packetDescs[m_packetsFilled] = *desc;
// Make sure the offset is relative to the start of the audio buffer
m_packetDescs[m_packetsFilled].mStartOffset = m_bytesFilled;
// keep track of bytes filled and packets filled
m_bytesFilled += packetSize;
m_packetsFilled++;
/* If filled our buffer with packets, then commit it to the system */
if (m_packetsFilled >= config->maxPacketDescs) {
enqueueBuffer();
}
}
}
/* private */
void Audio_Queue::cleanup()
{
if (!initialized()) {
AQ_TRACE("%s: warning: attempt to cleanup an uninitialized audio queue. return.\n", __PRETTY_FUNCTION__);
return;
}
Stream_Configuration *config = Stream_Configuration::configuration();
if (m_state != IDLE) {
AQ_TRACE("%s: attemping to cleanup the audio queue when it is still playing, force stopping\n",
__PRETTY_FUNCTION__);
AudioQueueRemovePropertyListener(m_outAQ,
kAudioQueueProperty_IsRunning,
audioQueueIsRunningCallback,
this);
AudioQueueStop(m_outAQ, true);
setState(IDLE);
}
if (AudioQueueDispose(m_outAQ, true) != 0) {
AQ_TRACE("%s: AudioQueueDispose failed!\n", __PRETTY_FUNCTION__);
}
m_outAQ = 0;
m_fillBufferIndex = m_bytesFilled = m_packetsFilled = m_buffersUsed = 0;
for (size_t i=0; i < config->bufferCount; i++) {
m_bufferInUse[i] = false;
}
m_lastError = noErr;
}
void Audio_Queue::setState(State state)
{
if (m_state == state) {
/* We are already in this state! */
return;
}
m_state = state;
if (m_delegate) {
m_delegate->audioQueueStateChanged(state);
}
}
void Audio_Queue::enqueueBuffer()
{
AQ_ASSERT(!m_bufferInUse[m_fillBufferIndex]);
Stream_Configuration *config = Stream_Configuration::configuration();
AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__);
pthread_mutex_lock(&m_bufferInUseMutex);
m_bufferInUse[m_fillBufferIndex] = true;
m_buffersUsed++;
// enqueue buffer
AudioQueueBufferRef fillBuf = m_audioQueueBuffer[m_fillBufferIndex];
fillBuf->mAudioDataByteSize = m_bytesFilled;
pthread_mutex_unlock(&m_bufferInUseMutex);
AQ_ASSERT(m_packetsFilled > 0);
OSStatus err = AudioQueueEnqueueBuffer(m_outAQ, fillBuf, m_packetsFilled, m_packetDescs);
if (!err) {
m_lastError = noErr;
start();
} else {
/* If we get an error here, it very likely means that the audio queue is no longer
running */
AQ_TRACE("%s: error in AudioQueueEnqueueBuffer\n", __PRETTY_FUNCTION__);
m_lastError = err;
return;
}
pthread_mutex_lock(&m_bufferInUseMutex);
// go to next buffer
if (++m_fillBufferIndex >= config->bufferCount) {
m_fillBufferIndex = 0;
}
// reset bytes filled
m_bytesFilled = 0;
// reset packets filled
m_packetsFilled = 0;
// wait until next buffer is not in use
while (m_bufferInUse[m_fillBufferIndex]) {
AQ_TRACE("waiting for buffer %u\n", (unsigned int)m_fillBufferIndex);
pthread_cond_wait(&m_bufferFreeCondition, &m_bufferInUseMutex);
}
pthread_mutex_unlock(&m_bufferInUseMutex);
}
// this is called by the audio queue when it has finished decoding our data.
// The buffer is now free to be reused.
void Audio_Queue::audioQueueOutputCallback(void *inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{
Audio_Queue *audioQueue = static_cast<Audio_Queue*>(inClientData);
Stream_Configuration *config = Stream_Configuration::configuration();
int bufIndex = -1;
for (unsigned int i = 0; i < config->bufferCount; ++i) {
if (inBuffer == audioQueue->m_audioQueueBuffer[i]) {
AQ_TRACE("findQueueBuffer %i\n", i);
bufIndex = i;
break;
}
}
if (bufIndex == -1) {
return;
}
pthread_mutex_lock(&audioQueue->m_bufferInUseMutex);
AQ_ASSERT(audioQueue->m_bufferInUse[bufIndex]);
audioQueue->m_bufferInUse[bufIndex] = false;
audioQueue->m_buffersUsed--;
AQ_TRACE("signaling buffer free for inuse %i....\n", bufIndex);
pthread_cond_signal(&audioQueue->m_bufferFreeCondition);
AQ_TRACE("signal sent!\n");
if (audioQueue->m_buffersUsed == 0 && audioQueue->m_delegate) {
AQ_LOCK_TRACE("audioQueueOutputCallback: unlock 2\n");
pthread_mutex_unlock(&audioQueue->m_bufferInUseMutex);
if (audioQueue->m_delegate) {
audioQueue->m_delegate->audioQueueBuffersEmpty();
}
} else {
pthread_mutex_unlock(&audioQueue->m_bufferInUseMutex);
if (audioQueue->m_delegate) {
audioQueue->m_delegate->audioQueueFinishedPlayingPacket();
}
}
AQ_LOCK_TRACE("audioQueueOutputCallback: unlock\n");
}
void Audio_Queue::audioQueueIsRunningCallback(void *inClientData, AudioQueueRef inAQ, AudioQueuePropertyID inID)
{
Audio_Queue *audioQueue = static_cast<Audio_Queue*>(inClientData);
AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__);
UInt32 running;
UInt32 output = sizeof(running);
OSStatus err = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &running, &output);
if (err) {
AQ_TRACE("%s: error in kAudioQueueProperty_IsRunning\n", __PRETTY_FUNCTION__);
return;
}
if (running) {
AQ_TRACE("audio queue running!\n");
audioQueue->setState(RUNNING);
} else {
audioQueue->setState(IDLE);
}
}
} // namespace astreamer

View File

@ -1,102 +0,0 @@
/*
* 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

File diff suppressed because it is too large Load Diff

View File

@ -1,255 +0,0 @@
/*
* 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

View File

@ -1,440 +0,0 @@
/*
* 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 "caching_stream.h"
#include "file_output.h"
#include "stream_configuration.h"
#include "file_stream.h"
//#define CS_DEBUG 1
#if !defined (CS_DEBUG)
#define CS_TRACE(...) do {} while (0)
#define CS_TRACE_CFSTRING(X) do {} while (0)
#define CS_TRACE_CFURL(X) do {} while (0)
#else
#define CS_TRACE(...) printf(__VA_ARGS__)
#define CS_TRACE_CFSTRING(X) CS_TRACE("%s\n", CFStringGetCStringPtr(X, kCFStringEncodingMacRoman))
#define CS_TRACE_CFURL(X) CS_TRACE_CFSTRING(CFURLGetString(X))
#endif
namespace astreamer {
Caching_Stream::Caching_Stream(Input_Stream *target) :
m_target(target),
m_fileOutput(0),
m_fileStream(new File_Stream()),
m_cacheable(false),
m_writable(false),
m_useCache(false),
m_cacheMetaDataWritten(false),
m_cacheIdentifier(0),
m_fileUrl(0),
m_metaDataUrl(0)
{
m_target->m_delegate = this;
m_fileStream->m_delegate = this;
}
Caching_Stream::~Caching_Stream()
{
if (m_target) {
delete m_target;
m_target = 0;
}
if (m_fileOutput) {
delete m_fileOutput;
m_fileOutput = 0;
}
if (m_fileStream) {
delete m_fileStream;
m_fileStream = 0;
}
if (m_cacheIdentifier) {
CFRelease(m_cacheIdentifier);
m_cacheIdentifier = 0;
}
if (m_fileUrl) {
CFRelease(m_fileUrl);
m_fileUrl = 0;
}
if (m_metaDataUrl) {
CFRelease(m_metaDataUrl);
m_fileUrl = 0;
}
}
CFURLRef Caching_Stream::createFileURLWithPath(CFStringRef path)
{
CFURLRef fileUrl = NULL;
if (!path) {
return fileUrl;
}
CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, path, NULL, NULL, kCFStringEncodingUTF8);
CFURLRef regularUrl = CFURLCreateWithString(kCFAllocatorDefault, (escapedPath ? escapedPath : path), NULL);
if (regularUrl) {
fileUrl = CFURLCreateFilePathURL(kCFAllocatorDefault, regularUrl, NULL);
CFRelease(regularUrl);
}
if (escapedPath) {
CFRelease(escapedPath);
}
return fileUrl;
}
void Caching_Stream::readMetaData()
{
if (!m_metaDataUrl) {
return;
}
CFReadStreamRef readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, m_metaDataUrl);
if (readStream) {
if (CFReadStreamOpen(readStream)) {
UInt8 buf[1024];
CFIndex bytesRead = CFReadStreamRead(readStream, buf, 1024);
if (bytesRead > 0) {
CFStringRef contentType = CFStringCreateWithBytes(kCFAllocatorDefault, buf, bytesRead, kCFStringEncodingUTF8, false);
if (contentType) {
if (m_fileStream) {
CS_TRACE("Setting the content type of the file stream based on the meta data\n");
CS_TRACE_CFSTRING(contentType);
m_fileStream->setContentType(contentType);
}
CFRelease(contentType);
}
}
CFReadStreamClose(readStream);
}
CFRelease(readStream);
}
}
Input_Stream_Position Caching_Stream::position()
{
if (m_useCache) {
return m_fileStream->position();
} else {
return m_target->position();
}
}
CFStringRef Caching_Stream::contentType()
{
if (m_useCache) {
return m_fileStream->contentType();
} else {
return m_target->contentType();
}
}
size_t Caching_Stream::contentLength()
{
if (m_useCache) {
return m_fileStream->contentLength();
} else {
return m_target->contentLength();
}
}
bool Caching_Stream::open()
{
bool status;
if (CFURLResourceIsReachable(m_metaDataUrl, NULL) &&
CFURLResourceIsReachable(m_fileUrl, NULL)) {
m_cacheable = false;
m_writable = false;
m_useCache = true;
m_cacheMetaDataWritten = false;
readMetaData();
CS_TRACE("Playing file from cache\n");
CS_TRACE_CFURL(m_fileUrl);
status = m_fileStream->open();
} else {
m_cacheable = true;
m_writable = false;
m_useCache = false;
m_cacheMetaDataWritten = false;
CS_TRACE("File not cached\n");
status = m_target->open();
}
return status;
}
bool Caching_Stream::open(const Input_Stream_Position& position)
{
bool status;
if (CFURLResourceIsReachable(m_metaDataUrl, NULL) &&
CFURLResourceIsReachable(m_fileUrl, NULL)) {
m_cacheable = false;
m_writable = false;
m_useCache = true;
m_cacheMetaDataWritten = false;
readMetaData();
CS_TRACE("Playing file from cache\n");
CS_TRACE_CFURL(m_fileUrl);
status = m_fileStream->open(position);
} else {
m_cacheable = false;
m_writable = false;
m_useCache = false;
m_cacheMetaDataWritten = false;
CS_TRACE("File not cached\n");
status = m_target->open(position);
}
return status;
}
void Caching_Stream::close()
{
m_fileStream->close();
m_target->close();
}
void Caching_Stream::setScheduledInRunLoop(bool scheduledInRunLoop)
{
if (m_useCache) {
m_fileStream->setScheduledInRunLoop(scheduledInRunLoop);
} else {
m_target->setScheduledInRunLoop(scheduledInRunLoop);
}
}
void Caching_Stream::setUrl(CFURLRef url)
{
m_target->setUrl(url);
}
void Caching_Stream::setCacheIdentifier(CFStringRef cacheIdentifier)
{
m_cacheIdentifier = CFStringCreateCopy(kCFAllocatorDefault, cacheIdentifier);
if (m_fileOutput) {
delete m_fileOutput;
m_fileOutput = 0;
}
Stream_Configuration *config = Stream_Configuration::configuration();
CFStringRef filePath = CFStringCreateWithFormat(NULL, NULL, CFSTR("file://%@/%@"), config->cacheDirectory, m_cacheIdentifier);
CFStringRef metaDataPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("file://%@/%@.metadata"), config->cacheDirectory, m_cacheIdentifier);
if (m_fileUrl) {
CFRelease(m_fileUrl);
m_fileUrl = 0;
}
if (m_metaDataUrl) {
CFRelease(m_metaDataUrl);
m_metaDataUrl = 0;
}
m_fileUrl = createFileURLWithPath(filePath);
m_metaDataUrl = createFileURLWithPath(metaDataPath);
m_fileStream->setUrl(m_fileUrl);
CFRelease(filePath);
CFRelease(metaDataPath);
}
bool Caching_Stream::canHandleUrl(CFURLRef url)
{
if (!url) {
return false;
}
CFStringRef scheme = CFURLCopyScheme(url);
if (scheme) {
if (CFStringCompare(scheme, CFSTR("http"), 0) == kCFCompareEqualTo) {
CFRelease(scheme);
// Using cache makes only sense for HTTP
return true;
}
CFRelease(scheme);
}
// Nothing else to server
return false;
}
/* ID3_Parser_Delegate */
void Caching_Stream::id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
{
if (m_delegate) {
m_delegate->streamMetaDataAvailable(metaData);
}
}
void Caching_Stream::id3tagSizeAvailable(UInt32 tagSize)
{
if (m_delegate) {
m_delegate->streamMetaDataByteSizeAvailable(tagSize);
}
}
/* Input_Stream_Delegate */
void Caching_Stream::streamIsReadyRead()
{
if (m_cacheable) {
// If the stream is cacheable (not seeked from some position)
// Check if the stream has a length. If there is no length,
// it is a continuous stream and thus cannot be cached.
m_cacheable = (m_target->contentLength() > 0);
}
#if CS_DEBUG
if (m_cacheable) CS_TRACE("Stream can be cached!\n");
else CS_TRACE("Stream cannot be cached\n");
#endif
if (m_delegate) {
m_delegate->streamIsReadyRead();
}
}
void Caching_Stream::streamHasBytesAvailable(UInt8 *data, UInt32 numBytes)
{
if (m_cacheable) {
if (numBytes > 0) {
if (!m_fileOutput) {
if (m_fileUrl) {
CS_TRACE("Caching started for stream\n");
m_fileOutput = new File_Output(m_fileUrl);
m_writable = true;
}
}
if (m_writable && m_fileOutput) {
m_writable &= (m_fileOutput->write(data, numBytes) > 0);
}
}
}
if (m_delegate) {
m_delegate->streamHasBytesAvailable(data, numBytes);
}
}
void Caching_Stream::streamEndEncountered()
{
if (m_fileOutput) {
delete m_fileOutput;
m_fileOutput = 0;
}
if (m_cacheable) {
if (m_writable) {
CS_TRACE("Successfully cached the stream\n");
CS_TRACE_CFURL(m_fileUrl);
// We only write the meta data if the stream was successfully streamed.
// In that way we can use the meta data as an indicator that there is a file to stream.
if (!m_cacheMetaDataWritten) {
CFWriteStreamRef writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, m_metaDataUrl);
if (writeStream) {
if (CFWriteStreamOpen(writeStream)) {
CFStringRef contentType = m_target->contentType();
UInt8 buf[1024];
CFIndex usedBytes = 0;
if (contentType) {
// It is possible that some streams don't provide a content type
CFStringGetBytes(contentType,
CFRangeMake(0, CFStringGetLength(contentType)),
kCFStringEncodingUTF8,
'?',
false,
buf,
1024,
&usedBytes);
}
if (usedBytes > 0) {
CS_TRACE("Writing the meta data\n");
CS_TRACE_CFSTRING(contentType);
CFWriteStreamWrite(writeStream, buf, usedBytes);
}
CFWriteStreamClose(writeStream);
}
CFRelease(writeStream);
}
m_cacheable = false;
m_writable = false;
m_useCache = true;
m_cacheMetaDataWritten = true;
}
}
}
if (m_delegate) {
m_delegate->streamEndEncountered();
}
}
void Caching_Stream::streamErrorOccurred(CFStringRef errorDesc)
{
if (m_delegate) {
m_delegate->streamErrorOccurred(errorDesc);
}
}
void Caching_Stream::streamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
{
if (m_delegate) {
m_delegate->streamMetaDataAvailable(metaData);
}
}
void Caching_Stream::streamMetaDataByteSizeAvailable(UInt32 sizeInBytes)
{
if (m_delegate) {
m_delegate->streamMetaDataByteSizeAvailable(sizeInBytes);
}
}
} // namespace astreamer

View File

@ -1,73 +0,0 @@
/*
* 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 */

View File

@ -1,30 +0,0 @@
/*
* 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

View File

@ -1,32 +0,0 @@
/*
* 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

View File

@ -1,405 +0,0 @@
/*
* 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_stream.h"
#include "stream_configuration.h"
namespace astreamer {
File_Stream::File_Stream() :
m_url(0),
m_readStream(0),
m_scheduledInRunLoop(false),
m_readPending(false),
m_fileReadBuffer(0),
m_id3Parser(new ID3_Parser()),
m_contentType(0)
{
m_id3Parser->m_delegate = this;
}
File_Stream::~File_Stream()
{
close();
if (m_fileReadBuffer) {
delete [] m_fileReadBuffer;
m_fileReadBuffer = 0;
}
if (m_url) {
CFRelease(m_url);
m_url = 0;
}
delete m_id3Parser;
m_id3Parser = 0;
if (m_contentType) {
CFRelease(m_contentType);
}
}
Input_Stream_Position File_Stream::position()
{
return m_position;
}
CFStringRef File_Stream::contentType()
{
if (m_contentType) {
// Use the provided content type
return m_contentType;
}
// Try to resolve the content type from the file
CFStringRef contentType = CFSTR("");
CFStringRef pathComponent = 0;
CFIndex len = 0;
CFRange range;
CFStringRef suffix = 0;
if (!m_url) {
goto done;
}
pathComponent = CFURLCopyLastPathComponent(m_url);
if (!pathComponent) {
goto done;
}
len = CFStringGetLength(pathComponent);
if (len > 5) {
range.length = 4;
range.location = len - 4;
suffix = CFStringCreateWithSubstring(kCFAllocatorDefault,
pathComponent,
range);
if (!suffix) {
goto done;
}
// TODO: we should do the content-type resolvation in a better way.
if (CFStringCompare(suffix, CFSTR(".mp3"), 0) == kCFCompareEqualTo) {
contentType = CFSTR("audio/mpeg");
} else if (CFStringCompare(suffix, CFSTR(".m4a"), 0) == kCFCompareEqualTo) {
contentType = CFSTR("audio/x-m4a");
} else if (CFStringCompare(suffix, CFSTR(".mp4"), 0) == kCFCompareEqualTo) {
contentType = CFSTR("audio/mp4");
} else if (CFStringCompare(suffix, CFSTR(".aac"), 0) == kCFCompareEqualTo) {
contentType = CFSTR("audio/aac");
}
}
done:
if (pathComponent) {
CFRelease(pathComponent);
}
if (suffix) {
CFRelease(suffix);
}
return contentType;
}
void File_Stream::setContentType(CFStringRef contentType)
{
if (m_contentType) {
CFRelease(m_contentType);
m_contentType = 0;
}
if (contentType) {
m_contentType = CFStringCreateCopy(kCFAllocatorDefault, contentType);
}
}
size_t File_Stream::contentLength()
{
CFNumberRef length = NULL;
CFErrorRef err = NULL;
if (CFURLCopyResourcePropertyForKey(m_url, kCFURLFileSizeKey, &length, &err)) {
CFIndex fileLength;
if (CFNumberGetValue(length, kCFNumberCFIndexType, &fileLength)) {
CFRelease(length);
return fileLength;
}
}
return 0;
}
bool File_Stream::open()
{
Input_Stream_Position position;
position.start = 0;
position.end = 0;
m_id3Parser->reset();
return open(position);
}
bool File_Stream::open(const Input_Stream_Position& position)
{
bool success = false;
CFStreamClientContext CTX = { 0, this, NULL, NULL, NULL };
/* Already opened a read stream, return */
if (m_readStream) {
goto out;
}
if (!m_url) {
goto out;
}
/* Reset state */
m_position = position;
m_readPending = false;
/* Failed to create a stream */
if (!(m_readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, m_url))) {
goto out;
}
if (m_position.start > 0) {
CFNumberRef position = CFNumberCreate(0, kCFNumberLongLongType, &m_position.start);
CFReadStreamSetProperty(m_readStream, kCFStreamPropertyFileCurrentOffset, position);
CFRelease(position);
}
if (!CFReadStreamSetClient(m_readStream, kCFStreamEventHasBytesAvailable |
kCFStreamEventEndEncountered |
kCFStreamEventErrorOccurred, readCallBack, &CTX)) {
CFRelease(m_readStream);
m_readStream = 0;
goto out;
}
setScheduledInRunLoop(true);
if (!CFReadStreamOpen(m_readStream)) {
/* Open failed: clean */
CFReadStreamSetClient(m_readStream, 0, NULL, NULL);
setScheduledInRunLoop(false);
if (m_readStream) {
CFRelease(m_readStream);
m_readStream = 0;
}
goto out;
}
success = true;
out:
if (success) {
if (m_delegate) {
m_delegate->streamIsReadyRead();
}
}
return success;
}
void File_Stream::close()
{
/* The stream has been already closed */
if (!m_readStream) {
return;
}
CFReadStreamSetClient(m_readStream, 0, NULL, NULL);
setScheduledInRunLoop(false);
CFReadStreamClose(m_readStream);
CFRelease(m_readStream);
m_readStream = 0;
}
void File_Stream::setScheduledInRunLoop(bool scheduledInRunLoop)
{
/* The stream has not been opened, or it has been already closed */
if (!m_readStream) {
return;
}
/* The state doesn't change */
if (m_scheduledInRunLoop == scheduledInRunLoop) {
return;
}
if (m_scheduledInRunLoop) {
CFReadStreamUnscheduleFromRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
} else {
if (m_readPending) {
m_readPending = false;
readCallBack(m_readStream, kCFStreamEventHasBytesAvailable, this);
}
CFReadStreamScheduleWithRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
m_scheduledInRunLoop = scheduledInRunLoop;
}
void File_Stream::setUrl(CFURLRef url)
{
if (m_url) {
CFRelease(m_url);
}
if (url) {
m_url = (CFURLRef)CFRetain(url);
} else {
m_url = NULL;
}
}
bool File_Stream::canHandleUrl(CFURLRef url)
{
if (!url) {
return false;
}
CFStringRef scheme = CFURLCopyScheme(url);
if (scheme) {
if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) {
CFRelease(scheme);
// The only scheme we claim to handle are the local files
return true;
}
CFRelease(scheme);
}
// We don't handle anything else but local files
return false;
}
/* ID3_Parser_Delegate */
void File_Stream::id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
{
if (m_delegate) {
m_delegate->streamMetaDataAvailable(metaData);
}
}
void File_Stream::id3tagSizeAvailable(UInt32 tagSize)
{
if (m_delegate) {
m_delegate->streamMetaDataByteSizeAvailable(tagSize);
}
}
void File_Stream::readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
File_Stream *THIS = static_cast<File_Stream*>(clientCallBackInfo);
Stream_Configuration *config = Stream_Configuration::configuration();
switch (eventType) {
case kCFStreamEventHasBytesAvailable: {
if (!THIS->m_fileReadBuffer) {
THIS->m_fileReadBuffer = new UInt8[config->httpConnectionBufferSize];
}
while (CFReadStreamHasBytesAvailable(stream)) {
if (!THIS->m_scheduledInRunLoop) {
/*
* This is critical - though the stream has data available,
* do not try to feed the audio queue with data, if it has
* indicated that it doesn't want more data due to buffers
* full.
*/
THIS->m_readPending = true;
break;
}
CFIndex bytesRead = CFReadStreamRead(stream, THIS->m_fileReadBuffer, config->httpConnectionBufferSize);
if (CFReadStreamGetStatus(stream) == kCFStreamStatusError ||
bytesRead < 0) {
if (THIS->m_delegate) {
CFStringRef reportedNetworkError = NULL;
CFErrorRef streamError = CFReadStreamCopyError(stream);
if (streamError) {
CFStringRef errorDesc = CFErrorCopyDescription(streamError);
if (errorDesc) {
reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc);
CFRelease(errorDesc);
}
CFRelease(streamError);
}
THIS->m_delegate->streamErrorOccurred(reportedNetworkError);
if (reportedNetworkError) {
CFRelease(reportedNetworkError);
}
}
break;
}
if (bytesRead > 0) {
if (THIS->m_delegate) {
THIS->m_delegate->streamHasBytesAvailable(THIS->m_fileReadBuffer, (UInt32)bytesRead);
}
if (THIS->m_id3Parser->wantData()) {
THIS->m_id3Parser->feedData(THIS->m_fileReadBuffer, (UInt32)bytesRead);
}
}
}
break;
}
case kCFStreamEventEndEncountered: {
if (THIS->m_delegate) {
THIS->m_delegate->streamEndEncountered();
}
break;
}
case kCFStreamEventErrorOccurred: {
if (THIS->m_delegate) {
CFStringRef reportedNetworkError = NULL;
CFErrorRef streamError = CFReadStreamCopyError(stream);
if (streamError) {
CFStringRef errorDesc = CFErrorCopyDescription(streamError);
if (errorDesc) {
reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc);
CFRelease(errorDesc);
}
CFRelease(streamError);
}
THIS->m_delegate->streamErrorOccurred(reportedNetworkError);
if (reportedNetworkError) {
CFRelease(reportedNetworkError);
}
}
break;
}
}
}
} // namespace astreamer

View File

@ -1,64 +0,0 @@
/*
* 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

View File

@ -1,908 +0,0 @@
/*
* 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 "http_stream.h"
#include "audio_queue.h"
#include "id3_parser.h"
#include "stream_configuration.h"
//#define HS_DEBUG 1
#if !defined (HS_DEBUG)
#define HS_TRACE(...) do {} while (0)
#define HS_TRACE_CFSTRING(X) do {} while (0)
#else
#define HS_TRACE(...) printf(__VA_ARGS__)
#define HS_TRACE_CFSTRING(X) HS_TRACE("%s\n", CFStringGetCStringPtr(X, kCFStringEncodingMacRoman))
#endif
/*
* Comment the following line to disable ID3 tag support:
*/
#define INCLUDE_ID3TAG_SUPPORT 1
namespace astreamer {
CFStringRef HTTP_Stream::httpRequestMethod = CFSTR("GET");
CFStringRef HTTP_Stream::httpUserAgentHeader = CFSTR("User-Agent");
CFStringRef HTTP_Stream::httpRangeHeader = CFSTR("Range");
CFStringRef HTTP_Stream::icyMetaDataHeader = CFSTR("Icy-MetaData");
CFStringRef HTTP_Stream::icyMetaDataValue = CFSTR("1"); /* always request ICY metadata, if available */
/* HTTP_Stream: public */
HTTP_Stream::HTTP_Stream() :
m_readStream(0),
m_scheduledInRunLoop(false),
m_readPending(false),
m_url(0),
m_httpHeadersParsed(false),
m_contentType(0),
m_contentLength(0),
m_bytesRead(0),
m_icyStream(false),
m_icyHeaderCR(false),
m_icyHeadersRead(false),
m_icyHeadersParsed(false),
m_icyName(0),
m_icyMetaDataInterval(0),
m_dataByteReadCount(0),
m_metaDataBytesRemaining(0),
m_httpReadBuffer(0),
m_icyReadBuffer(0),
m_id3Parser(new ID3_Parser())
{
m_id3Parser->m_delegate = this;
}
HTTP_Stream::~HTTP_Stream()
{
close();
for (std::vector<CFStringRef>::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) {
CFRelease(*h);
}
m_icyHeaderLines.clear();
if (m_contentType) {
CFRelease(m_contentType);
m_contentType = 0;
}
if (m_icyName) {
CFRelease(m_icyName);
m_icyName = 0;
}
if (m_httpReadBuffer) {
delete [] m_httpReadBuffer;
m_httpReadBuffer = 0;
}
if (m_icyReadBuffer) {
delete [] m_icyReadBuffer;
m_icyReadBuffer = 0;
}
if (m_url) {
CFRelease(m_url);
m_url = 0;
}
delete m_id3Parser;
m_id3Parser = 0;
}
Input_Stream_Position HTTP_Stream::position()
{
return m_position;
}
CFStringRef HTTP_Stream::contentType()
{
return m_contentType;
}
size_t HTTP_Stream::contentLength()
{
return m_contentLength;
}
bool HTTP_Stream::open()
{
Input_Stream_Position position;
position.start = 0;
position.end = 0;
m_contentLength = 0;
#ifdef INCLUDE_ID3TAG_SUPPORT
m_id3Parser->reset();
#endif
return open(position);
}
bool HTTP_Stream::open(const Input_Stream_Position& position)
{
bool success = false;
CFStreamClientContext CTX = { 0, this, NULL, NULL, NULL };
/* Already opened a read stream, return */
if (m_readStream) {
goto out;
}
/* Reset state */
m_position = position;
m_readPending = false;
m_httpHeadersParsed = false;
if (m_contentType) {
CFRelease(m_contentType);
m_contentType = NULL;
}
m_icyStream = false;
m_icyHeaderCR = false;
m_icyHeadersRead = false;
m_icyHeadersParsed = false;
if (m_icyName) {
CFRelease(m_icyName);
m_icyName = 0;
}
for (std::vector<CFStringRef>::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) {
CFRelease(*h);
}
m_icyHeaderLines.clear();
m_icyMetaDataInterval = 0;
m_dataByteReadCount = 0;
m_metaDataBytesRemaining = 0;
m_bytesRead = 0;
if (!m_url) {
goto out;
}
/* Failed to create a stream */
if (!(m_readStream = createReadStream(m_url))) {
goto out;
}
if (!CFReadStreamSetClient(m_readStream, kCFStreamEventHasBytesAvailable |
kCFStreamEventEndEncountered |
kCFStreamEventErrorOccurred, readCallBack, &CTX)) {
CFRelease(m_readStream);
m_readStream = 0;
goto out;
}
setScheduledInRunLoop(true);
if (!CFReadStreamOpen(m_readStream)) {
/* Open failed: clean */
CFReadStreamSetClient(m_readStream, 0, NULL, NULL);
setScheduledInRunLoop(false);
if (m_readStream) {
CFRelease(m_readStream);
m_readStream = 0;
}
goto out;
}
success = true;
out:
return success;
}
void HTTP_Stream::close()
{
/* The stream has been already closed */
if (!m_readStream) {
return;
}
CFReadStreamSetClient(m_readStream, 0, NULL, NULL);
setScheduledInRunLoop(false);
CFReadStreamClose(m_readStream);
CFRelease(m_readStream);
m_readStream = 0;
}
void HTTP_Stream::setScheduledInRunLoop(bool scheduledInRunLoop)
{
/* The stream has not been opened, or it has been already closed */
if (!m_readStream) {
return;
}
/* The state doesn't change */
if (m_scheduledInRunLoop == scheduledInRunLoop) {
return;
}
if (m_scheduledInRunLoop) {
CFReadStreamUnscheduleFromRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
} else {
if (m_readPending) {
m_readPending = false;
readCallBack(m_readStream, kCFStreamEventHasBytesAvailable, this);
}
CFReadStreamScheduleWithRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
m_scheduledInRunLoop = scheduledInRunLoop;
}
void HTTP_Stream::setUrl(CFURLRef url)
{
if (m_url) {
CFRelease(m_url);
}
if (url) {
m_url = (CFURLRef)CFRetain(url);
} else {
m_url = NULL;
}
}
bool HTTP_Stream::canHandleUrl(CFURLRef url)
{
if (!url) {
return false;
}
CFStringRef scheme = CFURLCopyScheme(url);
if (scheme) {
if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) {
CFRelease(scheme);
// The only scheme we claim not to handle are local files.
return false;
}
CFRelease(scheme);
}
return true;
}
void HTTP_Stream::id3metaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
{
if (m_delegate) {
m_delegate->streamMetaDataAvailable(metaData);
}
}
void HTTP_Stream::id3tagSizeAvailable(UInt32 tagSize)
{
if (m_delegate) {
m_delegate->streamMetaDataByteSizeAvailable(tagSize);
}
}
/* private */
CFReadStreamRef HTTP_Stream::createReadStream(CFURLRef url)
{
CFReadStreamRef readStream = 0;
CFHTTPMessageRef request = 0;
CFDictionaryRef proxySettings = 0;
Stream_Configuration *config = Stream_Configuration::configuration();
if (!(request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, httpRequestMethod, url, kCFHTTPVersion1_1))) {
goto out;
}
if (config->userAgent) {
CFHTTPMessageSetHeaderFieldValue(request, httpUserAgentHeader, config->userAgent);
}
CFHTTPMessageSetHeaderFieldValue(request, icyMetaDataHeader, icyMetaDataValue);
if (m_position.start > 0 && m_position.end > m_position.start) {
CFStringRef rangeHeaderValue = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("bytes=%llu-%llu"),
m_position.start,
m_position.end);
CFHTTPMessageSetHeaderFieldValue(request, httpRangeHeader, rangeHeaderValue);
CFRelease(rangeHeaderValue);
} else if (m_position.start > 0 && m_position.end < m_position.start) {
CFStringRef rangeHeaderValue = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("bytes=%llu-"),
m_position.start);
CFHTTPMessageSetHeaderFieldValue(request, httpRangeHeader, rangeHeaderValue);
CFRelease(rangeHeaderValue);
}
if (config->predefinedHttpHeaderValues) {
const CFIndex numKeys = CFDictionaryGetCount(config->predefinedHttpHeaderValues);
if (numKeys > 0) {
CFTypeRef *keys = (CFTypeRef *) malloc(numKeys * sizeof(CFTypeRef));
if (keys) {
CFDictionaryGetKeysAndValues(config->predefinedHttpHeaderValues, (const void **) keys, NULL);
for (CFIndex i=0; i < numKeys; i++) {
CFTypeRef key = keys[i];
if (CFGetTypeID(key) == CFStringGetTypeID()) {
const void *value = CFDictionaryGetValue(config->predefinedHttpHeaderValues, (const void *) key);
if (value) {
CFStringRef headerKey = (CFStringRef) key;
CFTypeRef valueRef = (CFTypeRef) value;
if (CFGetTypeID(valueRef) == CFStringGetTypeID()) {
CFStringRef headerValue = (CFStringRef) valueRef;
HS_TRACE("Setting predefined HTTP header ");
HS_TRACE_CFSTRING(headerKey);
HS_TRACE_CFSTRING(headerValue);
CFHTTPMessageSetHeaderFieldValue(request, headerKey, headerValue);
}
}
}
}
free(keys);
}
}
}
if (!(readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request))) {
goto out;
}
CFReadStreamSetProperty(readStream,
kCFStreamNetworkServiceType,
kCFStreamNetworkServiceTypeBackground);
CFReadStreamSetProperty(readStream,
kCFStreamPropertyHTTPShouldAutoredirect,
kCFBooleanTrue);
proxySettings = CFNetworkCopySystemProxySettings();
if (proxySettings) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxySettings);
CFRelease(proxySettings);
}
out:
if (request) {
CFRelease(request);
}
return readStream;
}
void HTTP_Stream::parseHttpHeadersIfNeeded(const UInt8 *buf, const CFIndex bufSize)
{
if (m_httpHeadersParsed) {
return;
}
m_httpHeadersParsed = true;
/* If the response has the "ICY 200 OK" string,
* we are dealing with the ShoutCast protocol.
* The HTTP headers won't be available.
*/
if (bufSize >= 10 &&
buf[0] == 0x49 && buf[1] == 0x43 && buf[2] == 0x59 &&
buf[3] == 0x20 && buf[4] == 0x32 && buf[5] == 0x30 &&
buf[6] == 0x30 && buf[7] == 0x20 && buf[8] == 0x4F &&
buf[9] == 0x4B) {
m_icyStream = true;
HS_TRACE("Detected an IceCast stream\n");
// This is an ICY stream, don't try to parse the HTTP headers
return;
}
HS_TRACE("A regular HTTP stream\n");
CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(m_readStream, kCFStreamPropertyHTTPResponseHeader);
CFIndex statusCode = 0;
if (response) {
/*
* If the server responded with the icy-metaint header, the response
* body will be encoded in the ShoutCast protocol.
*/
CFStringRef icyMetaIntString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("icy-metaint"));
if (icyMetaIntString) {
m_icyStream = true;
m_icyHeadersParsed = true;
m_icyHeadersRead = true;
m_icyMetaDataInterval = CFStringGetIntValue(icyMetaIntString);
CFRelease(icyMetaIntString);
}
HS_TRACE("icy-metaint: %zu\n", m_icyMetaDataInterval);
statusCode = CFHTTPMessageGetResponseStatusCode(response);
HS_TRACE("HTTP response code %zu", statusCode);
CFStringRef icyNameString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("icy-name"));
if (icyNameString) {
if (m_icyName) {
CFRelease(m_icyName);
}
m_icyName = icyNameString;
if (m_delegate) {
std::map<CFStringRef,CFStringRef> metadataMap;
metadataMap[CFSTR("IcecastStationName")] = CFStringCreateCopy(kCFAllocatorDefault, m_icyName);
m_delegate->streamMetaDataAvailable(metadataMap);
}
}
if (m_contentType) {
CFRelease(m_contentType);
}
m_contentType = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Content-Type"));
HS_TRACE("Content-type: ");
HS_TRACE_CFSTRING(m_contentType);
CFStringRef contentLengthString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Content-Length"));
if (contentLengthString) {
m_contentLength = CFStringGetIntValue(contentLengthString);
CFRelease(contentLengthString);
}
CFRelease(response);
}
if (m_delegate &&
(statusCode == 200 || statusCode == 206)) {
m_delegate->streamIsReadyRead();
} else {
if (m_delegate) {
CFStringRef statusCodeString = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("HTTP response code %d"),
(unsigned int)statusCode);
m_delegate->streamErrorOccurred(statusCodeString);
if (statusCodeString) {
CFRelease(statusCodeString);
}
}
}
}
void HTTP_Stream::parseICYStream(const UInt8 *buf, const CFIndex bufSize)
{
HS_TRACE("Parsing an IceCast stream, received %li bytes\n", bufSize);
CFIndex offset = 0;
CFIndex bytesFound = 0;
if (!m_icyHeadersRead) {
HS_TRACE("ICY headers not read, reading\n");
for (; offset < bufSize; offset++) {
if (m_icyHeaderCR && buf[offset] == '\n') {
if (bytesFound > 0) {
m_icyHeaderLines.push_back(createMetaDataStringWithMostReasonableEncoding(&buf[offset-bytesFound-1], bytesFound));
bytesFound = 0;
HS_TRACE_CFSTRING(m_icyHeaderLines[m_icyHeaderLines.size()-1]);
continue;
}
HS_TRACE("End of ICY headers\n");
m_icyHeadersRead = true;
break;
}
if (buf[offset] == '\r') {
m_icyHeaderCR = true;
continue;
} else {
m_icyHeaderCR = false;
}
bytesFound++;
}
} else if (!m_icyHeadersParsed) {
HS_TRACE("ICY headers not parsed, parsing\n");
const CFStringRef icyContentTypeHeader = CFSTR("content-type:");
const CFStringRef icyMetaDataHeader = CFSTR("icy-metaint:");
const CFStringRef icyNameHeader = CFSTR("icy-name:");
const CFIndex icyContenTypeHeaderLength = CFStringGetLength(icyContentTypeHeader);
const CFIndex icyMetaDataHeaderLength = CFStringGetLength(icyMetaDataHeader);
const CFIndex icyNameHeaderLength = CFStringGetLength(icyNameHeader);
for (std::vector<CFStringRef>::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) {
CFStringRef line = *h;
const CFIndex lineLength = CFStringGetLength(line);
if (lineLength == 0) {
continue;
}
HS_TRACE_CFSTRING(line);
if (CFStringCompareWithOptions(line,
icyContentTypeHeader,
CFRangeMake(0, icyContenTypeHeaderLength),
0) == kCFCompareEqualTo) {
if (m_contentType) {
CFRelease(m_contentType);
m_contentType = 0;
}
m_contentType = CFStringCreateWithSubstring(kCFAllocatorDefault,
line,
CFRangeMake(icyContenTypeHeaderLength, lineLength - icyContenTypeHeaderLength));
}
if (CFStringCompareWithOptions(line,
icyMetaDataHeader,
CFRangeMake(0, icyMetaDataHeaderLength),
0) == kCFCompareEqualTo) {
CFStringRef metadataInterval = CFStringCreateWithSubstring(kCFAllocatorDefault,
line,
CFRangeMake(icyMetaDataHeaderLength, lineLength - icyMetaDataHeaderLength));
if (metadataInterval) {
m_icyMetaDataInterval = CFStringGetIntValue(metadataInterval);
CFRelease(metadataInterval);
} else {
m_icyMetaDataInterval = 0;
}
}
if (CFStringCompareWithOptions(line,
icyNameHeader,
CFRangeMake(0, icyNameHeaderLength),
0) == kCFCompareEqualTo) {
if (m_icyName) {
CFRelease(m_icyName);
}
m_icyName = CFStringCreateWithSubstring(kCFAllocatorDefault,
line,
CFRangeMake(icyNameHeaderLength, lineLength - icyNameHeaderLength));
}
}
m_icyHeadersParsed = true;
offset++;
if (m_delegate) {
m_delegate->streamIsReadyRead();
}
}
Stream_Configuration *config = Stream_Configuration::configuration();
if (!m_icyReadBuffer) {
m_icyReadBuffer = new UInt8[config->httpConnectionBufferSize];
}
HS_TRACE("Reading ICY stream for playback\n");
UInt32 i=0;
for (; offset < bufSize; offset++) {
// is this a metadata byte?
if (m_metaDataBytesRemaining > 0) {
m_metaDataBytesRemaining--;
if (m_metaDataBytesRemaining == 0) {
m_dataByteReadCount = 0;
if (m_delegate && !m_icyMetaData.empty()) {
std::map<CFStringRef,CFStringRef> metadataMap;
CFStringRef metaData = createMetaDataStringWithMostReasonableEncoding(&m_icyMetaData[0],
m_icyMetaData.size());
if (!metaData) {
// Metadata encoding failed, cannot parse.
m_icyMetaData.clear();
continue;
}
CFArrayRef tokens = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault,
metaData,
CFSTR(";"));
for (CFIndex i=0, max=CFArrayGetCount(tokens); i < max; i++) {
CFStringRef token = (CFStringRef) CFArrayGetValueAtIndex(tokens, i);
CFRange foundRange;
if (CFStringFindWithOptions(token,
CFSTR("='"),
CFRangeMake(0, CFStringGetLength(token)),
NULL,
&foundRange) == true) {
CFRange keyRange = CFRangeMake(0, foundRange.location);
CFStringRef metadaKey = CFStringCreateWithSubstring(kCFAllocatorDefault,
token,
keyRange);
CFRange valueRange = CFRangeMake(foundRange.location + 2, CFStringGetLength(token) - keyRange.length - 3);
CFStringRef metadaValue = CFStringCreateWithSubstring(kCFAllocatorDefault,
token,
valueRange);
metadataMap[metadaKey] = metadaValue;
}
}
CFRelease(tokens);
CFRelease(metaData);
if (m_icyName) {
metadataMap[CFSTR("IcecastStationName")] = CFStringCreateCopy(kCFAllocatorDefault, m_icyName);
}
m_delegate->streamMetaDataAvailable(metadataMap);
}
m_icyMetaData.clear();
continue;
}
m_icyMetaData.push_back(buf[offset]);
continue;
}
// is this the interval byte?
if (m_icyMetaDataInterval > 0 && m_dataByteReadCount == m_icyMetaDataInterval) {
m_metaDataBytesRemaining = buf[offset] * 16;
if (m_metaDataBytesRemaining == 0) {
m_dataByteReadCount = 0;
}
continue;
}
// a data byte
m_dataByteReadCount++;
m_icyReadBuffer[i++] = buf[offset];
}
if (m_delegate && i > 0) {
m_delegate->streamHasBytesAvailable(m_icyReadBuffer, i);
}
}
#define TRY_ENCODING(STR,ENC) STR = CFStringCreateWithBytes(kCFAllocatorDefault, bytes, numBytes, ENC, false); \
if (STR != NULL) { return STR; }
CFStringRef HTTP_Stream::createMetaDataStringWithMostReasonableEncoding(const UInt8 *bytes, const CFIndex numBytes)
{
CFStringRef metaData;
TRY_ENCODING(metaData, kCFStringEncodingUTF8);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin1);
TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin1);
TRY_ENCODING(metaData, kCFStringEncodingNextStepLatin);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin2);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin3);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin4);
TRY_ENCODING(metaData, kCFStringEncodingISOLatinCyrillic);
TRY_ENCODING(metaData, kCFStringEncodingISOLatinArabic);
TRY_ENCODING(metaData, kCFStringEncodingISOLatinGreek);
TRY_ENCODING(metaData, kCFStringEncodingISOLatinHebrew);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin5);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin6);
TRY_ENCODING(metaData, kCFStringEncodingISOLatinThai);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin7);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin8);
TRY_ENCODING(metaData, kCFStringEncodingISOLatin9);
TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin2);
TRY_ENCODING(metaData, kCFStringEncodingWindowsCyrillic);
TRY_ENCODING(metaData, kCFStringEncodingWindowsGreek);
TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin5);
TRY_ENCODING(metaData, kCFStringEncodingWindowsHebrew);
TRY_ENCODING(metaData, kCFStringEncodingWindowsArabic);
TRY_ENCODING(metaData, kCFStringEncodingKOI8_R);
TRY_ENCODING(metaData, kCFStringEncodingBig5);
TRY_ENCODING(metaData, kCFStringEncodingASCII);
return metaData;
}
#undef TRY_ENCODING
void HTTP_Stream::readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
HTTP_Stream *THIS = static_cast<HTTP_Stream*>(clientCallBackInfo);
Stream_Configuration *config = Stream_Configuration::configuration();
CFStringRef reportedNetworkError = NULL;
switch (eventType) {
case kCFStreamEventHasBytesAvailable: {
if (!THIS->m_httpReadBuffer) {
THIS->m_httpReadBuffer = new UInt8[config->httpConnectionBufferSize];
}
while (CFReadStreamHasBytesAvailable(stream)) {
if (!THIS->m_scheduledInRunLoop) {
/*
* This is critical - though the stream has data available,
* do not try to feed the audio queue with data, if it has
* indicated that it doesn't want more data due to buffers
* full.
*/
THIS->m_readPending = true;
break;
}
CFIndex bytesRead = CFReadStreamRead(stream, THIS->m_httpReadBuffer, config->httpConnectionBufferSize);
if (CFReadStreamGetStatus(stream) == kCFStreamStatusError ||
bytesRead < 0) {
if (THIS->contentLength() > 0) {
/*
* Try to recover gracefully if we have a non-continuous stream
*/
Input_Stream_Position currentPosition = THIS->position();
Input_Stream_Position recoveryPosition;
recoveryPosition.start = currentPosition.start + THIS->m_bytesRead;
recoveryPosition.end = THIS->contentLength();
HS_TRACE("Recovering HTTP stream, start %llu\n", recoveryPosition.start);
THIS->open(recoveryPosition);
break;
}
CFErrorRef streamError = CFReadStreamCopyError(stream);
if (streamError) {
CFStringRef errorDesc = CFErrorCopyDescription(streamError);
if (errorDesc) {
reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc);
CFRelease(errorDesc);
}
CFRelease(streamError);
}
if (THIS->m_delegate) {
THIS->m_delegate->streamErrorOccurred(reportedNetworkError);
if (reportedNetworkError) {
CFRelease(reportedNetworkError);
reportedNetworkError = NULL;
}
}
break;
}
if (bytesRead > 0) {
THIS->m_bytesRead += bytesRead;
HS_TRACE("Read %li bytes, total %llu\n", bytesRead, THIS->m_bytesRead);
THIS->parseHttpHeadersIfNeeded(THIS->m_httpReadBuffer, bytesRead);
#ifdef INCLUDE_ID3TAG_SUPPORT
if (!THIS->m_icyStream && THIS->m_id3Parser->wantData()) {
THIS->m_id3Parser->feedData(THIS->m_httpReadBuffer, (UInt32)bytesRead);
}
#endif
if (THIS->m_icyStream) {
HS_TRACE("Parsing ICY stream\n");
THIS->parseICYStream(THIS->m_httpReadBuffer, bytesRead);
} else {
if (THIS->m_delegate) {
HS_TRACE("Not an ICY stream; calling the delegate back\n");
THIS->m_delegate->streamHasBytesAvailable(THIS->m_httpReadBuffer, (UInt32)bytesRead);
}
}
}
}
if (reportedNetworkError) {
CFRelease(reportedNetworkError);
reportedNetworkError = NULL;
}
break;
}
case kCFStreamEventEndEncountered: {
// This should concerns only non-continous streams
if (THIS->m_bytesRead < THIS->contentLength()) {
HS_TRACE("End of stream, but we have read only %llu bytes on a total of %li. Missing: %llu\n", THIS->m_bytesRead, THIS->contentLength(), (THIS->contentLength() - THIS->m_bytesRead));
Input_Stream_Position currentPosition = THIS->position();
Input_Stream_Position recoveryPosition;
recoveryPosition.start = currentPosition.start + THIS->m_bytesRead;
recoveryPosition.end = THIS->contentLength();
HS_TRACE("Reopen for the end of the file from byte position: %llu\n", recoveryPosition.start);
THIS->close();
THIS->open(recoveryPosition);
break;
}
if (THIS->m_delegate) {
THIS->m_delegate->streamEndEncountered();
}
break;
}
case kCFStreamEventErrorOccurred: {
if (THIS->m_delegate) {
CFStringRef reportedNetworkError = NULL;
CFErrorRef streamError = CFReadStreamCopyError(stream);
if (streamError) {
CFStringRef errorDesc = CFErrorCopyDescription(streamError);
if (errorDesc) {
reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc);
CFRelease(errorDesc);
}
CFRelease(streamError);
}
THIS->m_delegate->streamErrorOccurred(reportedNetworkError);
if (reportedNetworkError) {
CFRelease(reportedNetworkError);
}
}
break;
}
}
}
} // namespace astreamer

View File

@ -1,98 +0,0 @@
/*
* 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

View File

@ -1,536 +0,0 @@
/*
* 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 "id3_parser.h"
#include <vector>
//#define ID3_DEBUG 1
#if !defined ( ID3_DEBUG)
#define ID3_TRACE(...) do {} while (0)
#else
#define ID3_TRACE(...) printf(__VA_ARGS__)
#endif
namespace astreamer {
// Code from:
// http://www.opensource.apple.com/source/libsecurity_manifest/libsecurity_manifest-29384/lib/SecureDownloadInternal.c
// Returns a CFString containing the base64 representation of the data.
// boolean argument for whether to line wrap at 64 columns or not.
CFStringRef createBase64EncodedString(const UInt8* ptr, size_t len, int wrap) {
const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/=";
// base64 encoded data uses 4 ASCII characters to represent 3 octets.
// There can be up to two == at the end of the base64 data for padding.
// If we are line wrapping then we need space for one newline character
// every 64 characters of output.
// Rounded 4/3 up to 2 to avoid floating point math.
//CFIndex max_len = (2*len) + 2;
//if (wrap) len = len + ((2*len) / 64) + 1;
CFMutableStringRef string = CFStringCreateMutable(NULL, 0);
if (!string) return NULL;
/*
http://www.faqs.org/rfcs/rfc3548.html
+--first octet--+-second octet--+--third octet--+
|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-----------+---+-------+-------+---+-----------+
|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|
+--1.index--+--2.index--+--3.index--+--4.index--+
*/
int i = 0; // octet offset into input data
int column = 0; // output column number (used for line wrapping)
for (;;) {
UniChar c[16]; // buffer of characters to add to output
int j = 0; // offset to place next character in buffer
int index; // index into output alphabet
#define ADDCHAR(_X_) do { c[j++] = _X_; if (wrap && (++column == 64)) { column = 0; c[j++] = '\n'; } } while (0);
// 1.index
index = (ptr[i] >> 2) & 0x3F;
ADDCHAR(alphabet[index]);
// 2.index
index = (ptr[i] << 4) & 0x30;
if ((i+1) < len) {
index = index | ((ptr[i+1] >> 4) & 0x0F);
ADDCHAR(alphabet[index]);
} else { // end of input, pad as necessary
ADDCHAR(alphabet[index]);
ADDCHAR('=');
ADDCHAR('=');
}
// 3.index
if ((i+1) < len) {
index = (ptr[i+1] << 2) & 0x3C;
if ((i+2) < len) {
index = index | ((ptr[i+2] >> 6) & 0x03);
ADDCHAR(alphabet[index]);
} else { // end of input, pad as necessary
ADDCHAR(alphabet[index]);
ADDCHAR('=');
}
}
// 4.index
if ((i+2) < len) {
index = (ptr[i+2]) & 0x3F;
ADDCHAR(alphabet[index]);
}
CFStringAppendCharacters(string, c, j);
i += 3; // we processed 3 bytes of input
if (i >= len) {
// end of data, append newline if we haven't already
if (wrap && c[j-1] != '\n') {
c[0] = '\n';
CFStringAppendCharacters(string, c, 1);
}
break;
}
}
return string;
}
enum ID3_Parser_State {
ID3_Parser_State_Initial = 0,
ID3_Parser_State_Parse_Frames,
ID3_Parser_State_Tag_Parsed,
ID3_Parser_State_Not_Valid_Tag
};
/*
* =======================================
* Private class
* =======================================
*/
class ID3_Parser_Private {
public:
ID3_Parser_Private();
~ID3_Parser_Private();
bool wantData();
void feedData(UInt8 *data, UInt32 numBytes);
void setState(ID3_Parser_State state);
void reset();
CFStringRef parseContent(UInt32 framesize, UInt32 pos, CFStringEncoding encoding, bool byteOrderMark);
ID3_Parser *m_parser;
ID3_Parser_State m_state;
UInt32 m_bytesReceived;
UInt32 m_tagSize;
UInt8 m_majorVersion;
bool m_hasFooter;
bool m_usesUnsynchronisation;
bool m_usesExtendedHeader;
CFStringRef m_title;
CFStringRef m_performer;
CFStringRef m_coverArt;
std::vector<UInt8> m_tagData;
};
/*
* =======================================
* Private class implementation
* =======================================
*/
ID3_Parser_Private::ID3_Parser_Private() :
m_parser(0),
m_state(ID3_Parser_State_Initial),
m_bytesReceived(0),
m_tagSize(0),
m_majorVersion(0),
m_hasFooter(false),
m_usesUnsynchronisation(false),
m_usesExtendedHeader(false),
m_title(NULL),
m_performer(NULL),
m_coverArt(NULL)
{
}
ID3_Parser_Private::~ID3_Parser_Private()
{
if (m_performer) {
CFRelease(m_performer);
m_performer = NULL;
}
if (m_title) {
CFRelease(m_title);
m_title = NULL;
}
if (m_coverArt) {
CFRelease(m_coverArt);
m_coverArt = NULL;
}
}
bool ID3_Parser_Private::wantData()
{
if (m_state == ID3_Parser_State_Tag_Parsed) {
return false;
}
if (m_state == ID3_Parser_State_Not_Valid_Tag) {
return false;
}
return true;
}
void ID3_Parser_Private::feedData(UInt8 *data, UInt32 numBytes)
{
if (!wantData()) {
return;
}
m_bytesReceived += numBytes;
ID3_TRACE("received %i bytes, total bytes %i\n", numBytes, m_bytesReceived);
for (CFIndex i=0; i < numBytes; i++) {
m_tagData.push_back(data[i]);
}
bool enoughBytesToParse = true;
while (enoughBytesToParse) {
switch (m_state) {
case ID3_Parser_State_Initial: {
// Do we have enough bytes to determine if this is an ID3 tag or not?
if (m_bytesReceived <= 9) {
enoughBytesToParse = false;
break;
}
if (!(m_tagData[0] == 'I' &&
m_tagData[1] == 'D' &&
m_tagData[2] == '3')) {
ID3_TRACE("Not an ID3 tag, bailing out\n");
// Does not begin with the tag header; not an ID3 tag
setState(ID3_Parser_State_Not_Valid_Tag);
enoughBytesToParse = false;
break;
}
m_majorVersion = m_tagData[3];
// Currently support only id3v2.2 and 2.3
if (m_majorVersion != 2 && m_majorVersion != 3) {
ID3_TRACE("ID3v2.%i not supported by the parser\n", m_majorVersion);
setState(ID3_Parser_State_Not_Valid_Tag);
enoughBytesToParse = false;
break;
}
// Ignore the revision
// Parse the flags
if ((m_tagData[5] & 0x80) != 0) {
m_usesUnsynchronisation = true;
} else if ((m_tagData[5] & 0x40) != 0 && m_majorVersion >= 3) {
m_usesExtendedHeader = true;
} else if ((m_tagData[5] & 0x10) != 0 && m_majorVersion >= 3) {
m_hasFooter = true;
}
m_tagSize = ((m_tagData[6] & 0x7F) << 21) | ((m_tagData[7] & 0x7F) << 14) |
((m_tagData[8] & 0x7F) << 7) | (m_tagData[9] & 0x7F);
if (m_tagSize > 0) {
if (m_hasFooter) {
m_tagSize += 10;
}
m_tagSize += 10;
ID3_TRACE("tag size: %i\n", m_tagSize);
if (m_parser->m_delegate) {
m_parser->m_delegate->id3tagSizeAvailable(m_tagSize);
}
setState(ID3_Parser_State_Parse_Frames);
break;
}
setState(ID3_Parser_State_Not_Valid_Tag);
enoughBytesToParse = false;
break;
}
case ID3_Parser_State_Parse_Frames: {
// Do we have enough data to parse the frames?
if (m_tagData.size() < m_tagSize) {
ID3_TRACE("Not enough data received for parsing, have %lu bytes, need %i bytes\n",
m_tagData.size(),
m_tagSize);
enoughBytesToParse = false;
break;
}
UInt32 pos = 10;
// Do we have an extended header? If we do, skip it
if (m_usesExtendedHeader) {
UInt32 extendedHeaderSize = ((m_tagData[pos] << 21) |
(m_tagData[pos+1] << 14) |
(m_tagData[pos+2] << 7) |
m_tagData[pos+3]);
if (pos + extendedHeaderSize >= m_tagSize) {
setState(ID3_Parser_State_Not_Valid_Tag);
enoughBytesToParse = false;
break;
}
ID3_TRACE("Skipping extended header, size %i\n", extendedHeaderSize);
pos += extendedHeaderSize;
}
while (pos < m_tagSize) {
char frameName[5];
frameName[0] = m_tagData[pos];
frameName[1] = m_tagData[pos+1];
frameName[2] = m_tagData[pos+2];
if (m_majorVersion >= 3) {
frameName[3] = m_tagData[pos+3];
} else {
frameName[3] = 0;
}
frameName[4] = 0;
UInt32 framesize = 0;
if (m_majorVersion >= 3) {
pos += 4;
framesize = ((m_tagData[pos] << 21) |
(m_tagData[pos+1] << 14) |
(m_tagData[pos+2] << 7) |
m_tagData[pos+3]);
} else {
pos += 3;
framesize = ((m_tagData[pos] << 16) |
(m_tagData[pos+1] << 8) |
m_tagData[pos+2]);
}
if (framesize == 0) {
setState(ID3_Parser_State_Not_Valid_Tag);
enoughBytesToParse = false;
// Break from the loop and then out of the case context
goto ParseFramesExit;
}
if (m_majorVersion >= 3) {
pos += 6;
} else {
pos += 3;
}
CFStringEncoding encoding;
bool byteOrderMark = false;
if (m_tagData[pos] == 3) {
encoding = kCFStringEncodingUTF8;
} else if (m_tagData[pos] == 2) {
encoding = kCFStringEncodingUTF16BE;
} else if (m_tagData[pos] == 1) {
encoding = kCFStringEncodingUTF16;
byteOrderMark = true;
} else {
// ISO-8859-1 is the default encoding
encoding = kCFStringEncodingISOLatin1;
}
if (!strcmp(frameName, "TIT2") || !strcmp(frameName, "TT2")) {
if (m_title) {
CFRelease(m_title);
}
m_title = parseContent(framesize, pos + 1, encoding, byteOrderMark);
ID3_TRACE("ID3 title parsed: '%s'\n", CFStringGetCStringPtr(m_title, CFStringGetSystemEncoding()));
} else if (!strcmp(frameName, "TPE1") || !strcmp(frameName, "TP1")) {
if (m_performer) {
CFRelease(m_performer);
}
m_performer = parseContent(framesize, pos + 1, encoding, byteOrderMark);
ID3_TRACE("ID3 performer parsed: '%s'\n", CFStringGetCStringPtr(m_performer, CFStringGetSystemEncoding()));
} else if (!strcmp(frameName, "APIC")) {
char imageType[65] = {0};
size_t dataPos = pos+1;
for (int i=0; m_tagData[dataPos]; i++,dataPos++) {
imageType[i] = m_tagData[dataPos];
}
dataPos++;
if (!strcmp(imageType, "image/jpeg") ||
!strcmp(imageType, "image/png")) {
ID3_TRACE("Image type %s, parsing, dataPos %zu\n", imageType, dataPos);
// Skip the image description
while (!m_tagData[++dataPos]);
const size_t coverArtSize = framesize - ((dataPos - pos) + 5);
UInt8 *bytes = new UInt8[coverArtSize];
for (int i=0; i < coverArtSize; i++) {
bytes[i] = m_tagData[dataPos+i];
}
if (m_coverArt) {
CFRelease(m_coverArt);
}
m_coverArt = createBase64EncodedString(bytes, coverArtSize, 0);
delete [] bytes;
} else {
ID3_TRACE("%s is an unknown type for image data, skipping\n", imageType);
}
} else {
// Unknown/unhandled frame
ID3_TRACE("Unknown/unhandled frame: %s, size %i\n", frameName, framesize);
}
pos += framesize;
}
// Push out the metadata
if (m_parser->m_delegate) {
std::map<CFStringRef,CFStringRef> metadataMap;
if (m_performer && CFStringGetLength(m_performer) > 0) {
metadataMap[CFSTR("MPMediaItemPropertyArtist")] =
CFStringCreateCopy(kCFAllocatorDefault, m_performer);
}
if (m_title && CFStringGetLength(m_title) > 0) {
metadataMap[CFSTR("MPMediaItemPropertyTitle")] =
CFStringCreateCopy(kCFAllocatorDefault, m_title);
}
if (m_coverArt && CFStringGetLength(m_coverArt) > 0) {
metadataMap[CFSTR("CoverArt")] =
CFStringCreateCopy(kCFAllocatorDefault, m_coverArt);
}
m_parser->m_delegate->id3metaDataAvailable(metadataMap);
}
setState(ID3_Parser_State_Tag_Parsed);
enoughBytesToParse = false;
ParseFramesExit:
break;
}
default:
enoughBytesToParse = false;
break;
}
}
}
void ID3_Parser_Private::setState(astreamer::ID3_Parser_State state)
{
m_state = state;
}
void ID3_Parser_Private::reset()
{
m_state = ID3_Parser_State_Initial;
m_bytesReceived = 0;
m_tagSize = 0;
m_majorVersion = 0;
m_hasFooter = false;
m_usesUnsynchronisation = false;
m_usesExtendedHeader = false;
if (m_title) {
CFRelease(m_title);
m_title = NULL;
}
if (m_performer) {
CFRelease(m_performer);
m_performer = NULL;
}
if (m_coverArt) {
CFRelease(m_coverArt);
m_coverArt = NULL;
}
m_tagData.clear();
}
CFStringRef ID3_Parser_Private::parseContent(UInt32 framesize, UInt32 pos, CFStringEncoding encoding, bool byteOrderMark)
{
CFStringRef content = CFStringCreateWithBytes(kCFAllocatorDefault,
&m_tagData[pos],
framesize - 1,
encoding,
byteOrderMark);
return content;
}
/*
* =======================================
* ID3_Parser implementation
* =======================================
*/
ID3_Parser::ID3_Parser() :
m_delegate(0),
m_private(new ID3_Parser_Private())
{
m_private->m_parser = this;
}
ID3_Parser::~ID3_Parser()
{
delete m_private;
m_private = 0;
}
void ID3_Parser::reset()
{
m_private->reset();
}
bool ID3_Parser::wantData()
{
return m_private->wantData();
}
void ID3_Parser::feedData(UInt8 *data, UInt32 numBytes)
{
m_private->feedData(data, numBytes);
}
}

View File

@ -1,44 +0,0 @@
/*
* 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

View File

@ -1,21 +0,0 @@
/*
* 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()
{
}
}

View File

@ -1,56 +0,0 @@
/*
* 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

View File

@ -1,34 +0,0 @@
/*
* 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;
}
}

View File

@ -1,55 +0,0 @@
/*
* 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

View File

@ -1,53 +0,0 @@
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.

View File

@ -1,41 +0,0 @@
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.

10
Pods/Manifest.lock generated
View File

@ -1,14 +1,11 @@
PODS:
- Alamofire (5.9.1)
- DownloadButton (0.1.0)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- IQKeyboardManagerSwift (6.5.16)
- JXPagingView/Paging (2.1.3)
- JXSegmentedView (1.3.3)
- Kingfisher (7.11.0)
- MJRefresh (3.7.9)
- Reachability (3.7.6)
- SnapKit (5.7.1)
- SVProgressHUD (2.3.1):
- SVProgressHUD/Core (= 2.3.1)
@ -19,7 +16,6 @@ PODS:
DEPENDENCIES:
- Alamofire
- DownloadButton
- FreeStreamer
- IQKeyboardManagerSwift
- JXPagingView/Paging
- JXSegmentedView
@ -34,13 +30,11 @@ SPEC REPOS:
trunk:
- Alamofire
- DownloadButton
- FreeStreamer
- IQKeyboardManagerSwift
- JXPagingView
- JXSegmentedView
- Kingfisher
- MJRefresh
- Reachability
- SnapKit
- SVProgressHUD
- SwiftDate
@ -49,18 +43,16 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
DownloadButton: 49a21a89e0d7d1b42d9134f79aaa40e727cd57c3
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68
Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90
PODFILE CHECKSUM: 2aaf13c9ed7d2b78bcf21030bbbacf03b3a0212c
PODFILE CHECKSUM: 436104abc66aacc2c16f90adefe5230845e81453
COCOAPODS: 1.15.2

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3AAC0817EA4DC8BD9C0046F50078BF9"
BuildableName = "FreeStreamer.framework"
BlueprintName = "FreeStreamer"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2787856C227A709315E3C9C4355A440"
BuildableName = "Reachability_Privacy.bundle"
BlueprintName = "Reachability-Reachability_Privacy"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CAA047C0F5E4106F3904E8497FA17F97"
BuildableName = "Reachability.framework"
BlueprintName = "Reachability"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -8,155 +8,96 @@
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>Alamofire.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>DownloadButton-DownloadButton.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>DownloadButton.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>FreeStreamer.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>IQKeyboardManagerSwift.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>5</integer>
</dict>
<key>JXPagingView-JXPagingView.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
</dict>
<key>JXPagingView.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
</dict>
<key>JXSegmentedView-JXSegmentedView.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>9</integer>
</dict>
<key>JXSegmentedView.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>8</integer>
</dict>
<key>Kingfisher-Kingfisher.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>11</integer>
</dict>
<key>Kingfisher.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>10</integer>
</dict>
<key>MJRefresh-MJRefresh.Privacy.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>13</integer>
</dict>
<key>MJRefresh.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>12</integer>
</dict>
<key>Pods-relax.offline.mp3.music.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>14</integer>
</dict>
<key>Reachability-Reachability_Privacy.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>16</integer>
</dict>
<key>Reachability.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>15</integer>
</dict>
<key>SVProgressHUD.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>19</integer>
</dict>
<key>SnapKit-SnapKit_Privacy.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>18</integer>
</dict>
<key>SnapKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>17</integer>
</dict>
<key>SwiftDate.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>20</integer>
</dict>
<key>Tiercel.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>21</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -1,14 +0,0 @@
<?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>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>

View File

@ -1,24 +0,0 @@
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.

View File

@ -1,159 +0,0 @@
[![Reference Status](https://www.versioneye.com/objective-c/reachability/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/reachability/references)
[![build-status](https://github.com/tonymillion/Reachability/actions/workflows/CI.yml/badge.svg)](https://github.com/tonymillion/Reachability/actions)
# **WARNING** there have been reports of apps being rejected when Reachability is used in a framework. The only solution to this so far is to rename the class.
# 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*
**BEFORE YOU OPEN A BUG ABOUT iOS6/iOS5 build errors, use Tag 3.2 or 3.1 as they support assign types**
## 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.
#### In Objective-C
```objc
// 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];
```
### In Swift 3
```swift
import Reachability
var reach: Reachability?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Allocate a reachability object
self.reach = Reachability.forInternetConnection()
// Set the blocks
self.reach!.reachableBlock = {
(reach: Reachability?) -> Void in
// 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:
DispatchQueue.main.async {
print("REACHABLE!")
}
}
self.reach!.unreachableBlock = {
(reach: Reachability?) -> Void in
print("UNREACHABLE!")
}
self.reach!.startNotifier()
return true
}
```
### `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).
#### In Objective-C
```objc
// 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];
```
#### In Swift 3
```swift
import Reachability
var reach: Reachability?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Allocate a reachability object
self.reach = Reachability.forInternetConnection()
// Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA
self.reach!.reachableOnWWAN = false
// Here we set up a NSNotification observer. The Reachability that caused the notification
// is passed in the object parameter
NotificationCenter.default.addObserver(
self,
selector: #selector(reachabilityChanged),
name: NSNotification.Name.reachabilityChanged,
object: nil
)
self.reach!.startNotifier()
return true
}
func reachabilityChanged(notification: NSNotification) {
if self.reach!.isReachableViaWiFi() || self.reach!.isReachableViaWWAN() {
print("Service available!!!")
} else {
print("No service available!!!")
}
}
```
## 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!".

View File

@ -1,103 +0,0 @@
/*
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>
//! Project version number for MacOSReachability.
FOUNDATION_EXPORT double ReachabilityVersionNumber;
//! Project version string for MacOSReachability.
FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[];
/**
* 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);
typedef void (^NetworkReachability)(Reachability * reachability, SCNetworkConnectionFlags flags);
@interface Reachability : NSObject
@property (nonatomic, copy) NetworkReachable reachableBlock;
@property (nonatomic, copy) NetworkUnreachable unreachableBlock;
@property (nonatomic, copy) NetworkReachability reachabilityBlock;
@property (nonatomic, assign) BOOL reachableOnWWAN;
+(instancetype)reachabilityWithHostname:(NSString*)hostname;
// This is identical to the function above, but is here to maintain
//compatibility with Apples original code. (see .m)
+(instancetype)reachabilityWithHostName:(NSString*)hostname;
+(instancetype)reachabilityForInternetConnection;
+(instancetype)reachabilityWithAddress:(void *)hostAddress;
+(instancetype)reachabilityForLocalWiFi;
+(instancetype)reachabilityWithURL:(NSURL*)url;
-(instancetype)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

View File

@ -1,508 +0,0 @@
/*
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 "Reachability.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";
@interface Reachability ()
@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
@property (nonatomic, strong) id reachabilityObject;
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
@end
static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags)
{
return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
#if TARGET_OS_IPHONE
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
#else
'X',
#endif
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
}
// Start listening for reachability notifications on the current run loop
static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target)
Reachability *reachability = ((__bridge Reachability*)info);
// We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
// but what the heck eh?
@autoreleasepool
{
[reachability reachabilityChanged:flags];
}
}
@implementation Reachability
#pragma mark - Class Constructor Methods
+(instancetype)reachabilityWithHostName:(NSString*)hostname
{
return [Reachability reachabilityWithHostname:hostname];
}
+(instancetype)reachabilityWithHostname:(NSString*)hostname
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityWithAddress:(void *)hostAddress
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityForInternetConnection
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
+(instancetype)reachabilityForLocalWiFi
{
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
return [self reachabilityWithAddress:&localWifiAddress];
}
+(instancetype)reachabilityWithURL:(NSURL*)url
{
id reachability;
NSString *host = url.host;
BOOL isIpAddress = [self isIpAddress:host];
if (isIpAddress)
{
NSNumber *port = url.port ?: [url.scheme isEqualToString:@"https"] ? @(443) : @(80);
struct sockaddr_in address;
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
address.sin_port = htons([port intValue]);
address.sin_addr.s_addr = inet_addr([host UTF8String]);
reachability = [self reachabilityWithAddress:&address];
}
else
{
reachability = [self reachabilityWithHostname:host];
}
return reachability;
}
+(BOOL)isIpAddress:(NSString*)host
{
struct in_addr pin;
return 1 == inet_aton([host UTF8String], &pin);
}
// Initialization methods
-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
-(void)dealloc
{
[self stopNotifier];
if(self.reachabilityRef)
{
CFRelease(self.reachabilityRef);
self.reachabilityRef = nil;
}
self.reachableBlock = nil;
self.unreachableBlock = nil;
self.reachabilityBlock = nil;
self.reachabilitySerialQueue = nil;
}
#pragma mark - Notifier Methods
// Notifier
// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
-(BOOL)startNotifier
{
// allow start notifier to be called multiple times
if(self.reachabilityObject && (self.reachabilityObject == self))
{
return YES;
}
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
context.info = (__bridge void *)self;
if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
{
// Set it as our reachability queue, which will retain the queue
if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
{
// this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
// woah
self.reachabilityObject = self;
return YES;
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif
// UH OH - FAILURE - stop any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
}
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
}
// if we get here we fail at the internet
self.reachabilityObject = nil;
return NO;
}
-(void)stopNotifier
{
// First stop, any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
// Unregister target from the GCD serial dispatch queue.
SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
self.reachabilityObject = nil;
}
#pragma mark - reachability tests
// This is for the case where you flick the airplane mode;
// you end up getting something like this:
//Reachability: WR ct-----
//Reachability: -- -------
//Reachability: WR ct-----
//Reachability: -- -------
// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
BOOL connectionUP = YES;
if(!(flags & kSCNetworkReachabilityFlagsReachable))
connectionUP = NO;
if( (flags & testcase) == testcase )
connectionUP = NO;
#if TARGET_OS_IPHONE
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
// We're on 3G.
if(!self.reachableOnWWAN)
{
// We don't want to connect when on 3G.
connectionUP = NO;
}
}
#endif
return connectionUP;
}
-(BOOL)isReachable
{
SCNetworkReachabilityFlags flags;
if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
return NO;
return [self isReachableWithFlags:flags];
}
-(BOOL)isReachableViaWWAN
{
#if TARGET_OS_IPHONE
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're REACHABLE
if(flags & kSCNetworkReachabilityFlagsReachable)
{
// Now, check we're on WWAN
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
return YES;
}
}
}
#endif
return NO;
}
-(BOOL)isReachableViaWiFi
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're reachable
if((flags & kSCNetworkReachabilityFlagsReachable))
{
#if TARGET_OS_IPHONE
// Check we're NOT on WWAN
if((flags & kSCNetworkReachabilityFlagsIsWWAN))
{
return NO;
}
#endif
return YES;
}
}
return NO;
}
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired
{
return [self connectionRequired];
}
-(BOOL)connectionRequired
{
SCNetworkReachabilityFlags flags;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
}
return NO;
}
// Is user intervention required?
-(BOOL)isInterventionRequired
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired));
}
return NO;
}
#pragma mark - reachability status stuff
-(NetworkStatus)currentReachabilityStatus
{
if([self isReachable])
{
if([self isReachableViaWiFi])
return ReachableViaWiFi;
#if TARGET_OS_IPHONE
return ReachableViaWWAN;
#endif
}
return NotReachable;
}
-(SCNetworkReachabilityFlags)reachabilityFlags
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return flags;
}
return 0;
}
-(NSString*)currentReachabilityString
{
NetworkStatus temp = [self currentReachabilityStatus];
if(temp == ReachableViaWWAN)
{
// Updated for the fact that we have CDMA phones now!
return NSLocalizedString(@"Cellular", @"");
}
if (temp == ReachableViaWiFi)
{
return NSLocalizedString(@"WiFi", @"");
}
return NSLocalizedString(@"No Connection", @"");
}
-(NSString*)currentReachabilityFlags
{
return reachabilityFlags([self reachabilityFlags]);
}
#pragma mark - Callback function calls this method
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
if([self isReachableWithFlags:flags])
{
if(self.reachableBlock)
{
self.reachableBlock(self);
}
}
else
{
if(self.unreachableBlock)
{
self.unreachableBlock(self);
}
}
if(self.reachabilityBlock)
{
self.reachabilityBlock(self, flags);
}
// this makes sure the change notification happens on the MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
object:self];
});
}
#pragma mark - Debug Description
- (NSString *) description
{
NSString *description = [NSString stringWithFormat:@"<%@: %p (%@)>",
NSStringFromClass([self class]), self, [self currentReachabilityFlags]];
return description;
}
@end

View File

@ -1,26 +0,0 @@
<?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>${PODS_DEVELOPMENT_LANGUAGE}</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>

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_FreeStreamer : NSObject
@end
@implementation PodsDummy_FreeStreamer
@end

View File

@ -1,12 +0,0 @@
#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

View File

@ -1,23 +0,0 @@
#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[];

View File

@ -1,15 +0,0 @@
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_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
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

View File

@ -1,6 +0,0 @@
framework module FreeStreamer {
umbrella header "FreeStreamer-umbrella.h"
export *
module * { export * }
}

View File

@ -1,15 +0,0 @@
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_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
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

View File

@ -47,63 +47,6 @@ Contact details
Email: wk.katunin@gmail.com
Site: https://ru.linkedin.com/in/pavelkatunin
## 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.
## IQKeyboardManagerSwift
MIT License
@ -228,20 +171,6 @@ 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.
## SVProgressHUD
MIT License

View File

@ -70,69 +70,6 @@ Site: https://ru.linkedin.com/in/pavelkatunin</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</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>MIT License
@ -287,26 +224,6 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</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>MIT License

View File

@ -1,13 +1,11 @@
${PODS_ROOT}/Target Support Files/Pods-relax.offline.mp3.music/Pods-relax.offline.mp3.music-frameworks.sh
${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework
${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework
${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework
${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework
${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework
${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework

View File

@ -1,12 +1,10 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXPagingView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXSegmentedView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework

View File

@ -1,13 +1,11 @@
${PODS_ROOT}/Target Support Files/Pods-relax.offline.mp3.music/Pods-relax.offline.mp3.music-frameworks.sh
${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework
${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework
${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework
${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework
${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework
${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework

View File

@ -1,12 +1,10 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXPagingView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXSegmentedView.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework

View File

@ -178,13 +178,11 @@ code_sign_if_enabled() {
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework"
@ -193,13 +191,11 @@ fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework"

View File

@ -0,0 +1,3 @@
${PODS_ROOT}/Target Support Files/Pods-relax.offline.mp3.music/Pods-relax.offline.mp3.music-resources.sh
${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle
${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle

View File

@ -0,0 +1,2 @@
${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMobileAdsResources.bundle
${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/UserMessagingPlatformResources.bundle

View File

@ -0,0 +1,3 @@
${PODS_ROOT}/Target Support Files/Pods-relax.offline.mp3.music/Pods-relax.offline.mp3.music-resources.sh
${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle
${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle

View File

@ -0,0 +1,2 @@
${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMobileAdsResources.bundle
${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/UserMessagingPlatformResources.bundle

View File

@ -0,0 +1,131 @@
#!/bin/sh
set -e
set -u
set -o pipefail
function on_error {
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
}
trap 'on_error $LINENO' ERR
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
> "$RESOURCES_TO_COPY"
XCASSET_FILES=()
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
1)
TARGET_DEVICE_ARGS="--target-device iphone"
;;
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
4)
TARGET_DEVICE_ARGS="--target-device watch"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
install_resource()
{
if [[ "$1" = /* ]] ; then
RESOURCE_PATH="$1"
else
RESOURCE_PATH="${PODS_ROOT}/$1"
fi
if [[ ! -e "$RESOURCE_PATH" ]] ; then
cat << EOM
error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
EOM
exit 1
fi
case $RESOURCE_PATH in
*.storyboard)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.xib)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.framework)
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
;;
*.xcdatamodel)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
;;
*.xcdatamodeld)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
;;
*.xcmappingmodel)
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
echo "$RESOURCE_PATH" || true
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
;;
esac
}
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle"
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi

View File

@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" $(SDKROOT)/usr/include/libxml2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"c++" -l"swiftCoreGraphics" -l"xml2" -framework "AVFoundation" -framework "Accelerate" -framework "Alamofire" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "DownloadButton" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "MediaPlayer" -framework "QuartzCore" -framework "Reachability" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI"
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreGraphics" -framework "DownloadButton" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" $(SDKROOT)/usr/include/libxml2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"c++" -l"swiftCoreGraphics" -l"xml2" -framework "AVFoundation" -framework "Accelerate" -framework "Alamofire" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "DownloadButton" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "MediaPlayer" -framework "QuartzCore" -framework "Reachability" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI"
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreGraphics" -framework "DownloadButton" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -1,26 +0,0 @@
<?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>${PODS_DEVELOPMENT_LANGUAGE}</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.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Reachability : NSObject
@end
@implementation PodsDummy_Reachability
@end

View File

@ -1,12 +0,0 @@
#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

View File

@ -1,17 +0,0 @@
#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[];

View File

@ -1,13 +0,0 @@
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_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
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

View File

@ -1,6 +0,0 @@
framework module Reachability {
umbrella header "Reachability-umbrella.h"
export *
module * { export * }
}

View File

@ -1,13 +0,0 @@
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_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
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

View File

@ -1,24 +0,0 @@
<?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>${PODS_DEVELOPMENT_LANGUAGE}</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>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
46C4D5B84AF3CA6292152C70 /* Pods_relax_offline_mp3_music.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DB1E9160CFEC3A0E35F11BF /* Pods_relax_offline_mp3_music.framework */; };
CB000F442C12AD2300B0FC0A /* GoogleMobileAds in Frameworks */ = {isa = PBXBuildFile; productRef = CB000F432C12AD2300B0FC0A /* GoogleMobileAds */; };
CBAFCAE62C0A10500054500E /* MP_BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F22C0A10500054500E /* MP_BaseViewController.swift */; };
CBAFCAE72C0A10500054500E /* MP_LunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F32C0A10500054500E /* MP_LunchViewController.swift */; };
CBAFCAE82C0A10500054500E /* MP_LunchViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBAFC9F42C0A10500054500E /* MP_LunchViewController.xib */; };
@ -430,6 +431,7 @@
files = (
CBAFCBAD2C0A10DA0054500E /* FirebaseCrashlytics in Frameworks */,
CBAFCBAB2C0A10DA0054500E /* FirebaseAnalytics in Frameworks */,
CB000F442C12AD2300B0FC0A /* GoogleMobileAds in Frameworks */,
46C4D5B84AF3CA6292152C70 /* Pods_relax_offline_mp3_music.framework in Frameworks */,
CBAFCBAF2C0A10DA0054500E /* FirebaseRemoteConfig in Frameworks */,
);
@ -932,10 +934,10 @@
CBAFCAC82C0A10500054500E /* Home(音乐资源列表) */ = {
isa = PBXGroup;
children = (
CBAFCAC22C0A10500054500E /* MPSideA_CountTimerViewController.swift */,
CBAFCAC32C0A10500054500E /* MPSideA_CountTimerViewController.xib */,
CBAFCAC42C0A10500054500E /* MPSideA_HomeViewController.swift */,
CBAFCAC52C0A10500054500E /* MPSideA_HomeViewController.xib */,
CBAFCAC22C0A10500054500E /* MPSideA_CountTimerViewController.swift */,
CBAFCAC32C0A10500054500E /* MPSideA_CountTimerViewController.xib */,
CBAFCAC62C0A10500054500E /* MPSideA_PlayerViewController.swift */,
CBAFCAC72C0A10500054500E /* MPSideA_PlayerViewController.xib */,
);
@ -1089,6 +1091,7 @@
CBAFCBAA2C0A10DA0054500E /* FirebaseAnalytics */,
CBAFCBAC2C0A10DA0054500E /* FirebaseCrashlytics */,
CBAFCBAE2C0A10DA0054500E /* FirebaseRemoteConfig */,
CB000F432C12AD2300B0FC0A /* GoogleMobileAds */,
);
productName = relax.offline.mp3.music;
productReference = CBC2D6E82BFDF3D700E17703 /* relax.offline.mp3.music.app */;
@ -1120,6 +1123,7 @@
mainGroup = CBC2D6DF2BFDF3D700E17703;
packageReferences = (
CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
CB000F422C12AD2300B0FC0A /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */,
);
productRefGroup = CBC2D6E92BFDF3D700E17703 /* Products */;
projectDirPath = "";
@ -1559,7 +1563,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.4.1;
CURRENT_PROJECT_VERSION = 1.0.5.1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = T93S37G27F;
GENERATE_INFOPLIST_FILE = YES;
@ -1578,7 +1582,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.5;
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1600,7 +1604,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.4.1;
CURRENT_PROJECT_VERSION = 1.0.5.1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = T93S37G27F;
GENERATE_INFOPLIST_FILE = YES;
@ -1619,7 +1623,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.5;
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1657,6 +1661,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
CB000F422C12AD2300B0FC0A /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 11.5.0;
};
};
CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
@ -1668,6 +1680,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
CB000F432C12AD2300B0FC0A /* GoogleMobileAds */ = {
isa = XCSwiftPackageProductDependency;
package = CB000F422C12AD2300B0FC0A /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */;
productName = GoogleMobileAds;
};
CBAFCBAA2C0A10DA0054500E /* FirebaseAnalytics */ = {
isa = XCSwiftPackageProductDependency;
package = CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;

View File

@ -1,5 +1,5 @@
{
"originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
"originHash" : "03651f2e356682d9ff3c19f48721f41adee49ee676f5dbdcab8120359ace4980",
"pins" : [
{
"identity" : "abseil-cpp-binary",
@ -109,6 +109,24 @@
"version" : "2.4.0"
}
},
{
"identity" : "swift-package-manager-google-mobile-ads",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
"state" : {
"revision" : "746253bf8803826e4de14c9e619739733ea8fb3b",
"version" : "11.5.0"
}
},
{
"identity" : "swift-package-manager-google-user-messaging-platform",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git",
"state" : {
"revision" : "9b68aa69fb508f0274853e226c734151a973c7b7",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",

View File

@ -10,15 +10,17 @@ import CoreData
import AVFoundation
import Alamofire
import Tiercel
import FirebaseCore
import Firebase
@_exported import IQKeyboardManagerSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//
private var showStatus:Bool!
//
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//FireBase
//FireBase
FirebaseApp.configure()
//
MP_DownloadManager.shared.cancelAllTasksIfNeeded()
@ -39,7 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
MP_DownloadManager.shared.session.completionHandler = completionHandler
}
}
//
private func setAudioSupport(){
//
@ -71,6 +72,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
///A
func switch_aSide() {
guard showStatus != false else {
return
}
let tabBarVC = MPSideA_TabBarController()
//
let transtition = CATransition()
@ -79,9 +83,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.layer.add(transtition, forKey: "aSide.easeOut")
window?.rootViewController = tabBarVC
window?.makeKeyAndVisible()
showStatus = false
}
//b
func switch_positive() {
guard showStatus != true else {
return
}
//A
MPSideA_MediaCenterManager.shared.destroySideA()
MPSideA_VolumeManager.shared.destroySideA()
let tabBarVC = MPPositive_TabBarController()
//
let transtition = CATransition()
@ -90,6 +101,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.layer.add(transtition, forKey: "positive.easeOut")
window?.rootViewController = tabBarVC
window?.makeKeyAndVisible()
showStatus = true
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {

View File

@ -16,11 +16,13 @@ class MP_LunchViewController: UIViewController {
//
private var timer:CADisplayLink!
//
private lazy var maxTimes:TimeInterval = 6
private lazy var maxTimes:TimeInterval = 7
//
private lazy var currentTimes:TimeInterval = 0
//
private var completionBlock:(() -> Void)?
//A/B
private var isBeen:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .init(hex: "#000000")
@ -31,20 +33,9 @@ class MP_LunchViewController: UIViewController {
timer.add(to: RunLoop.current, forMode: .common)
//
timer.isPaused = false
// self.completionBlock = {
// DispatchQueue.main.async {
// [weak self] in
// guard let self = self else {return}
// //
// timer.isPaused = true
// //
// accessAppdelegate.switch_positive()
// //
// MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
// }
// }
}
deinit {
timer.isPaused = true
//
timer.invalidate()
timer = nil
@ -52,16 +43,9 @@ class MP_LunchViewController: UIViewController {
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
//
requestTrackingAuthorization { idfa in
// if let idfa = idfa {
// print("IDFA: \(idfa)")
// StartManager.shared.idfaid = idfa
// print("Stored IDFA: \(StartManager.shared.idfaid ?? "N/A")")
// } else {
// print("IDFA is not available or tracking authorization denied.")
// }
}
}
}
@ -82,41 +66,20 @@ class MP_LunchViewController: UIViewController {
//
progressView.setProgress(value)
}
//A/B
if isBeen == false {
//
isBeen = true
switchAOrBAction()
}
}else {
//
timer.isPaused = true
MP_AnalyticsManager.shared.getOpenStatus { [weak self] open in
guard let self = self else {return}
if open {
//ip
MP_NetWorkManager.shared.requestIPInfo { statu in
if statu == true {
//b
print("BLog")
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
//
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
}
}else {
print("ALog")
//A
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
guard MP_NetWorkManager.shared.netWorkStatu != .reachable else {
if completionBlock != nil {
completionBlock!()
}else {
//A
print("ALog")
//A
DispatchQueue.main.async {
@ -128,6 +91,142 @@ class MP_LunchViewController: UIViewController {
accessAppdelegate.switch_aSide()
}
}
return
}
//
if completionBlock != nil {
completionBlock!()
}else {
//A
print("ALog")
//A
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
}
//a/b
private func switchAOrBAction() {
//B
guard UserDefaults.standard.bool(forKey: "MP_Into_B") != true else {
//B
self.completionBlock = {
//b
print("BLog")
//BB
UserDefaults.standard.set(true, forKey: "MP_Into_B")
//B
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
}
return
}
MP_NetWorkManager.shared.performTaskNetWrokAvailable {
[weak self] in
guard let self = self else {return}
//B
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
//
MP_AnalyticsManager.shared.getOpenStatus { [weak self] open in
guard let self = self else {return}
if open {
//IP
//ip
MP_NetWorkManager.shared.requestIPInfo { statu in
if statu == true {
//b
print("BLog")
//BB
UserDefaults.standard.set(true, forKey: "MP_Into_B")
//
if self.maxTimes > self.currentTimes {
//
self.completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
}
}else {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
}
}else {
print("ALog")
//A
if self.maxTimes > self.currentTimes {
self.completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}else {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
}
}else {
print("ALog")
//A
if self.maxTimes > self.currentTimes {
self.completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}else {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
}
}
}

View File

@ -84,21 +84,38 @@ class MPSideA_MediaCenterManager {
static let shared = MPSideA_MediaCenterManager()
//MARK: -
//
private var player:AVPlayer?
var player:AVPlayer?
//
private var center:MPRemoteCommandCenter?
var center:MPRemoteCommandCenter?
//GCD
private var countTimer: DispatchSourceTimer?
var countTimer: DispatchSourceTimer?
//()
private var monitor:AVAudioRecorder?
var monitor:AVAudioRecorder?
//
private var monitorSetingsDic:[String : Any]?
var monitorSetingsDic:[String : Any]?
//GCD
private var monitorTimer: DispatchSourceTimer?
var monitorTimer: DispatchSourceTimer?
//A
func destroySideA() {
//
NotificationCenter.default.removeObserver(self)
//
player = nil
//
countTimer?.cancel()
countTimer = nil
//
monitor = nil
monitorTimer?.cancel()
monitorTimer = nil
center?.playCommand.removeTarget(self)
center?.pauseCommand.removeTarget(self)
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
}
//MARK: -
///
private var music:MPSideA_MusicModel?
var music:MPSideA_MusicModel?
///
func getMusic() -> MPSideA_MusicModel?{
return music

View File

@ -78,4 +78,10 @@ class MPSideA_VolumeManager:NSObject {
}
}
}
//A
func destroySideA() {
//
AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: "outputVolume")
destroyVolume()
}
}

View File

@ -22,15 +22,15 @@ class MP_NetWorkManager: NSObject {
private lazy var MPSession:Session = {
let configuration = URLSessionConfiguration.af.default
//4
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 30
configuration.timeoutIntervalForRequest = 12
configuration.timeoutIntervalForResource = 12
return Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
}()
///
private lazy var PlayerSeesion:Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 30
configuration.timeoutIntervalForRequest = 12
configuration.timeoutIntervalForResource = 12
//4
configuration.httpMaximumConnectionsPerHost = 4
return Alamofire.Session(configuration: configuration, interceptor: MP_CustomRetrier())
@ -81,8 +81,6 @@ class MP_NetWorkManager: NSObject {
case reachable = "网络可用"
}
///
private let reachabilityManager:NetworkReachabilityManager = NetworkReachabilityManager(host: "https://music.youtube.com/")!
///
private var monitor:NWPathMonitor
///
private var isReach:Bool = false
@ -107,8 +105,6 @@ class MP_NetWorkManager: NSObject {
}
}
}
//
private var debounceTimer: Timer?
//MARK: -
//访
private var visitorData:String?
@ -155,7 +151,11 @@ class MP_NetWorkManager: NSObject {
}
}
}
//MARK: - GCD
//MARK: - GCD
///
private let MPNetWorkqueue = DispatchQueue(label: "MPNetWorkManager")
///A/B
private var executionQueue: [() -> Void] = []
///-
private var browseQueque:DispatchQueue?
///-
@ -167,13 +167,11 @@ class MP_NetWorkManager: NSObject {
private override init() {
self.monitor = NWPathMonitor()
super.init()
}
//MARK: -
///
func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) {
let monitor = NWPathMonitor()
switch netWorkStatu {
case .reachable:
DispatchQueue.main.async {
@ -212,23 +210,7 @@ class MP_NetWorkManager: NSObject {
return
}
isReach = true
// //ping
// reachabilityManager.startListening(onQueue: .main, onUpdatePerforming: { [weak self] status in
// guard let self = self else {return}
// var newStatu:NetWorkStatus = .unknown
// switch status {
// case .unknown://
// newStatu = .unknown
// case .notReachable://
// newStatu = .notReachable
// case .reachable(.ethernetOrWiFi), .reachable(.cellular)://
// newStatu = .reachable
// }
// //
// self.handleDebouncedNetworkStatusChange(newStatu)
// })
let queue = DispatchQueue(label: "MPNetWorkManager")
monitor.start(queue: queue)
monitor.start(queue: MPNetWorkqueue)
monitor.pathUpdateHandler = { [weak self] path in
self?.updateNetworkStatus(path)
}
@ -236,25 +218,27 @@ class MP_NetWorkManager: NSObject {
private func updateNetworkStatus(_ path: NWPath) {
if path.status == .satisfied {
netWorkStatu = .reachable
executePendingTasks()
} else {
netWorkStatu = .notReachable
}
}
func stopListening() {
reachabilityManager.stopListening()
isReach = false
//AB
func performTaskNetWrokAvailable(_ task: @escaping() -> Void) {
//
if netWorkStatu == .reachable {
//
task()
}else {
//
executionQueue.append(task)
}
}
private func handleDebouncedNetworkStatusChange(_ newStatus: NetWorkStatus) {
//
debounceTimer?.invalidate()
//
debounceTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
guard let self = self else { return }
//
if newStatus != self.netWorkStatu {
self.netWorkStatu = newStatus
}
//
private func executePendingTasks() {
while !executionQueue.isEmpty {
let task = executionQueue.removeFirst()
task()
}
}
}
@ -2010,7 +1994,7 @@ class MP_CustomRetrier: RequestInterceptor {
//
private var retryCounts: [String: Int] = [:]
//
private let maxRetryCount: Int = 3
private let maxRetryCount: Int = 2
//
private let retryInterval: TimeInterval = 1.0
//

View File

@ -9,7 +9,6 @@ import UIKit
import AVFoundation
import MediaPlayer
import AVKit
import FreeStreamer
import Kingfisher
///
enum MP_PlayerStateType:Int {
@ -620,7 +619,7 @@ class MP_PlayerManager:NSObject{
//
center!.playCommand.addTarget(handler: { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
if loadPlayer.currentVideo != nil && playState == .Pause {
resume()
return .success
}else {
@ -630,7 +629,7 @@ class MP_PlayerManager:NSObject{
//
center!.pauseCommand.addTarget(handler: { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
if loadPlayer.currentVideo != nil && playState == .Playing {
pause()
return .success
}else {
@ -640,7 +639,7 @@ class MP_PlayerManager:NSObject{
//
center!.previousTrackCommand.addTarget { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
if loadPlayer.currentVideo != nil && MP_NetWorkManager.shared.netWorkStatu == .reachable {
previousEvent()
return .success
}else {
@ -650,7 +649,7 @@ class MP_PlayerManager:NSObject{
//
center!.nextTrackCommand.addTarget { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
if loadPlayer.currentVideo != nil && MP_NetWorkManager.shared.netWorkStatu == .reachable {
nextEvent()
return .success
}else {
@ -660,7 +659,9 @@ class MP_PlayerManager:NSObject{
//
center?.changePlaybackPositionCommand.addTarget(handler: { [weak self] event in
guard let self = self else { return .noActionableNowPlayingItem}
guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
return .noActionableNowPlayingItem
}
guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
return .commandFailed
}

View File

@ -17,18 +17,32 @@ class MPPositive_BrowseLoadViewModel: NSObject {
MP_NetWorkManager.shared.browseRequestStateBlock = {
[weak self] (lists,isCompleted) in
guard let self = self else {return}
//
browseModuleLists.append(contentsOf: lists)
//
browseModuleLists = browseModuleLists.filter{($0.items.count != 0)}
if isCompleted == true {
//
if let index = browseModuleLists.firstIndex(where: {$0.items.first?.browseItem.pageType == "MUSIC_VIDEO_TYPE_OMV"}) {
let removedElement = browseModuleLists.remove(at: index)
browseModuleLists.append(removedElement)
}
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
browseModuleLists.append(contentsOf: lists)
//
browseModuleLists = browseModuleLists.filter{($0.items.count != 0)}
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_browses_reload)
}
if isCompleted == true {
//
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
[weak self] in
guard let self = self else {
return
}
//
if let index = browseModuleLists.firstIndex(where: {$0.items.first?.browseItem.pageType == "MUSIC_VIDEO_TYPE_OMV"}) {
//
let removedElement = browseModuleLists.remove(at: index)
//
browseModuleLists.append(removedElement)
}
NotificationCenter.notificationKey.post(notificationName: .positive_browses_reload)
})
//
MP_AnalyticsManager.shared.home_b_module_showsucces_actionAction()
}

View File

@ -43,12 +43,17 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
// //
// MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
NotificationCenter.notificationKey.add(observer: self, selector: #selector(reloadAction(_ :)), notificationName: .positive_browses_reload)
MP_HUD.loading()
if MPPositive_BrowseLoadViewModel.shared.browseModuleLists.count == 0 {
MP_HUD.loading()
}
errorBlock = {
[weak self] in
guard let self = self else {
return
}
guard MPPositive_BrowseLoadViewModel.shared.browseModuleLists.count == 0 else {
return
}
//navView
view.subviews.forEach { item in
if item != self.navView {
@ -75,6 +80,14 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
//
requestTrackingAuthorization { idfa in
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

View File

@ -20,7 +20,7 @@ class MPPositive_MoreContentViewController: MPPositive_BaseViewController {
switch self {
case .Single:
layout.itemSize = .init(width: 168*width, height: 134*width)
layout.minimumInteritemSpacing = 5*width
layout.minimumInteritemSpacing = 2*width
layout.minimumLineSpacing = 32*width
case .List:
layout.itemSize = .init(width: 162*width, height: 210*width)

View File

@ -349,6 +349,7 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
coverView.downloadButton.isUserInteractionEnabled = !(MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false)
coverView.collectionSongBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isCollection ?? false
coverView.restoreDownloadProgress()
switchPlayTypeBtnIcon(typeBtn)
// activityIndicator.isHidden = true
// activityIndicator.stopAnimating()
}

View File

@ -38,6 +38,14 @@ class MPSideA_HomeViewController: MPSideA_BaseViewController {
self?.reload()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
//
requestTrackingAuthorization { idfa in
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)