瀏覽代碼

feeling music

gu 7 年之前
當前提交
2ef6f80c24
共有 100 個文件被更改,包括 13578 次插入0 次删除
  1. 208 0
      AudioPlayer/AudioPlayer.h
  2. 2279 0
      AudioPlayer/AudioPlayer.m
  3. 45 0
      AudioPlayer/AutoRecoveringHttpDataSource.h
  4. 236 0
      AudioPlayer/AutoRecoveringHttpDataSource.m
  5. 57 0
      AudioPlayer/CoreFoundationDataSource.h
  6. 161 0
      AudioPlayer/CoreFoundationDataSource.m
  7. 66 0
      AudioPlayer/DataSource.h
  8. 82 0
      AudioPlayer/DataSource.m
  9. 43 0
      AudioPlayer/DataSourceWrapper.h
  10. 120 0
      AudioPlayer/DataSourceWrapper.m
  11. 54 0
      AudioPlayer/HttpDataSource.h
  12. 252 0
      AudioPlayer/HttpDataSource.m
  13. 51 0
      AudioPlayer/LocalFileDataSource.h
  14. 214 0
      AudioPlayer/LocalFileDataSource.m
  15. 27 0
      DropDownChooseProtocol.h
  16. 31 0
      DropDownListView.h
  17. 239 0
      DropDownListView.m
  18. 72 0
      FMDB2_1/DBDaoHelper.h
  19. 331 0
      FMDB2_1/DBDaoHelper.m
  20. 13 0
      FMDB2_1/DBHelper.h
  21. 31 0
      FMDB2_1/DBHelper.m
  22. 218 0
      FMDB2_1/FMDatabase.h
  23. 1219 0
      FMDB2_1/FMDatabase.m
  24. 37 0
      FMDB2_1/FMDatabaseAdditions.h
  25. 163 0
      FMDB2_1/FMDatabaseAdditions.m
  26. 75 0
      FMDB2_1/FMDatabasePool.h
  27. 244 0
      FMDB2_1/FMDatabasePool.m
  28. 38 0
      FMDB2_1/FMDatabaseQueue.h
  29. 176 0
      FMDB2_1/FMDatabaseQueue.m
  30. 104 0
      FMDB2_1/FMResultSet.h
  31. 413 0
      FMDB2_1/FMResultSet.m
  32. 26 0
      FMDB2_1/extra/FMDatabase+InMemoryOnDiskIO.h
  33. 96 0
      FMDB2_1/extra/FMDatabase+InMemoryOnDiskIO.m
  34. 81 0
      FSAudioController.h
  35. 272 0
      FSAudioController.m
  36. 161 0
      FSAudioStream.h
  37. 496 0
      FSAudioStream.mm
  38. 97 0
      FSCheckContentTypeRequest.h
  39. 174 0
      FSCheckContentTypeRequest.m
  40. 61 0
      FSParsePlaylistRequest.h
  41. 259 0
      FSParsePlaylistRequest.m
  42. 26 0
      FSParseRssPodcastFeedRequest.h
  43. 61 0
      FSParseRssPodcastFeedRequest.m
  44. 30 0
      FSPlaylistItem.h
  45. 39 0
      FSPlaylistItem.m
  46. 98 0
      FSXMLHttpRequest.h
  47. 269 0
      FSXMLHttpRequest.m
  48. 1321 0
      FreeMusic.xcodeproj/project.pbxproj
  49. 7 0
      FreeMusic.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  50. 二進制
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/gulizan.xcuserdatad/UserInterfaceState.xcuserstate
  51. 二進制
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/jiamingyan.xcuserdatad/UserInterfaceState.xcuserstate
  52. 二進制
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/julie.xcuserdatad/UserInterfaceState.xcuserstate
  53. 10 0
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/julie.xcuserdatad/WorkspaceSettings.xcsettings
  54. 二進制
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/yanjiaming.xcuserdatad/UserInterfaceState.xcuserstate
  55. 二進制
      FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/zhaojianguo.xcuserdatad/UserInterfaceState.xcuserstate
  56. 101 0
      FreeMusic.xcodeproj/xcuserdata/gulizan.xcuserdatad/xcschemes/FreeMusic.xcscheme
  57. 27 0
      FreeMusic.xcodeproj/xcuserdata/gulizan.xcuserdatad/xcschemes/xcschememanagement.plist
  58. 101 0
      FreeMusic.xcodeproj/xcuserdata/jiamingyan.xcuserdatad/xcschemes/FreeMusic.xcscheme
  59. 27 0
      FreeMusic.xcodeproj/xcuserdata/jiamingyan.xcuserdatad/xcschemes/xcschememanagement.plist
  60. 5 0
      FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  61. 112 0
      FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcschemes/FreeMusic.xcscheme
  62. 27 0
      FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcschemes/xcschememanagement.plist
  63. 101 0
      FreeMusic.xcodeproj/xcuserdata/yanjiaming.xcuserdatad/xcschemes/FreeMusic.xcscheme
  64. 27 0
      FreeMusic.xcodeproj/xcuserdata/yanjiaming.xcuserdatad/xcschemes/xcschememanagement.plist
  65. 5 0
      FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  66. 96 0
      FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcschemes/FreeMusic.xcscheme
  67. 27 0
      FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcschemes/xcschememanagement.plist
  68. 28 0
      FreeMusic/AppDelegate.h
  69. 134 0
      FreeMusic/AppDelegate.m
  70. 16 0
      FreeMusic/DownViewController.h
  71. 104 0
      FreeMusic/DownViewController.m
  72. 23 0
      FreeMusic/FMBaseViewController.h
  73. 72 0
      FreeMusic/FMBaseViewController.m
  74. 16 0
      FreeMusic/FMHomeViewController.h
  75. 180 0
      FreeMusic/FMHomeViewController.m
  76. 12 0
      FreeMusic/FMLoadMoreFooterView.h
  77. 37 0
      FreeMusic/FMLoadMoreFooterView.m
  78. 20 0
      FreeMusic/FMLrcView.h
  79. 161 0
      FreeMusic/FMLrcView.m
  80. 21 0
      FreeMusic/FMMainTableViewCell.h
  81. 61 0
      FreeMusic/FMMainTableViewCell.m
  82. 16 0
      FreeMusic/FMMainViewController.h
  83. 159 0
      FreeMusic/FMMainViewController.m
  84. 23 0
      FreeMusic/FMMusicViewController.h
  85. 289 0
      FreeMusic/FMMusicViewController.m
  86. 17 0
      FreeMusic/FMMySongModel.h
  87. 24 0
      FreeMusic/FMMySongModel.m
  88. 15 0
      FreeMusic/FMPAImageView.h
  89. 63 0
      FreeMusic/FMPAImageView.m
  90. 17 0
      FreeMusic/FMPlayingListViewController.h
  91. 56 0
      FreeMusic/FMPlayingListViewController.m
  92. 38 0
      FreeMusic/FMSingerModel.h
  93. 47 0
      FreeMusic/FMSingerModel.m
  94. 18 0
      FreeMusic/FMSingerSongListViewController.h
  95. 201 0
      FreeMusic/FMSingerSongListViewController.m
  96. 51 0
      FreeMusic/FMSongListModel.h
  97. 34 0
      FreeMusic/FMSongListModel.m
  98. 18 0
      FreeMusic/FMSongListTableViewCell.h
  99. 68 0
      FreeMusic/FMSongListTableViewCell.m
  100. 0 0
      FreeMusic/FMSongModel.h

+ 208 - 0
AudioPlayer/AudioPlayer.h

@@ -0,0 +1,208 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Inspired by Matt Gallagher's AudioStreamer:
+ https://github.com/mattgallagher/AudioStreamer
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen''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 <COPYRIGHT HOLDER> 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 <pthread.h>
+#import "DataSource.h"
+#include <AudioToolbox/AudioToolbox.h>
+
+#define AudioPlayerDefaultNumberOfAudioQueueBuffers (2 * 1024)
+
+typedef enum
+{
+	AudioPlayerInternalStateInitialised = 0,
+    AudioPlayerInternalStateRunning = 1,
+    AudioPlayerInternalStatePlaying = (1 << 1) | AudioPlayerInternalStateRunning,
+	AudioPlayerInternalStateStartingThread = (1 << 2) | AudioPlayerInternalStateRunning,
+	AudioPlayerInternalStateWaitingForData = (1 << 3) | AudioPlayerInternalStateRunning,
+    AudioPlayerInternalStateWaitingForQueueToStart = (1 << 4) | AudioPlayerInternalStateRunning,
+    AudioPlayerInternalStatePaused = (1 << 5) | AudioPlayerInternalStateRunning,
+    AudioPlayerInternalStateRebuffering = (1 << 6) | AudioPlayerInternalStateRunning,
+    AudioPlayerInternalStateStopping = (1 << 7),
+    AudioPlayerInternalStateStopped = (1 << 8),
+    AudioPlayerInternalStateDisposed = (1 << 9),
+    AudioPlayerInternalStateError = (1 << 10)
+}
+AudioPlayerInternalState;
+
+typedef enum
+{
+    AudioPlayerStateReady,
+    AudioPlayerStateRunning = 1,
+    AudioPlayerStatePlaying = (1 << 1) | AudioPlayerStateRunning,
+    AudioPlayerStatePaused = (1 << 2) | AudioPlayerStateRunning,
+    AudioPlayerStateStopped = (1 << 3),
+    AudioPlayerStateError = (1 << 4),
+    AudioPlayerStateDisposed = (1 << 5)
+}
+AudioPlayerState;
+
+typedef enum
+{
+	AudioPlayerStopReasonNoStop = 0,
+	AudioPlayerStopReasonEof,
+	AudioPlayerStopReasonUserAction,
+    AudioPlayerStopReasonUserActionFlushStop
+}
+AudioPlayerStopReason;
+
+typedef enum
+{
+	AudioPlayerErrorNone = 0,
+	AudioPlayerErrorDataSource,
+    AudioPlayerErrorStreamParseBytesFailed,
+    AudioPlayerErrorDataNotFound,
+    AudioPlayerErrorQueueStartFailed,
+    AudioPlayerErrorQueuePauseFailed,
+    AudioPlayerErrorUnknownBuffer,
+    AudioPlayerErrorQueueStopFailed,
+    AudioPlayerErrorOther
+}
+AudioPlayerErrorCode;
+
+@class AudioPlayer;
+
+@protocol AudioPlayerDelegate <NSObject>
+-(void) audioPlayer:(AudioPlayer*)audioPlayer stateChanged:(AudioPlayerState)state;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer didEncounterError:(AudioPlayerErrorCode)errorCode;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer didStartPlayingQueueItemId:(NSObject*)queueItemId;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject*)queueItemId;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer didFinishPlayingQueueItemId:(NSObject*)queueItemId withReason:(AudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration;
+@optional
+-(void) audioPlayer:(AudioPlayer*)audioPlayer logInfo:(NSString*)line;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer internalStateChanged:(AudioPlayerInternalState)state;
+-(void) audioPlayer:(AudioPlayer*)audioPlayer didCancelQueuedItems:(NSArray*)queuedItems;
+@end
+
+@class QueueEntry;
+
+typedef struct
+{
+    AudioQueueBufferRef ref;
+    int bufferIndex;
+}
+AudioQueueBufferRefLookupEntry;
+
+@interface AudioPlayer : NSObject<DataSourceDelegate>
+{
+@private
+    UInt8* readBuffer;
+    int readBufferSize;
+	
+    NSOperationQueue* fastApiQueue;
+    
+    QueueEntry* currentlyPlayingEntry;
+    QueueEntry* currentlyReadingEntry;
+    
+    NSMutableArray* upcomingQueue;
+    NSMutableArray* bufferingQueue;
+    
+    AudioQueueBufferRef* audioQueueBuffer;
+    AudioQueueBufferRefLookupEntry* audioQueueBufferLookup;
+    unsigned int audioQueueBufferRefLookupCount;
+    unsigned int audioQueueBufferCount;
+    AudioStreamPacketDescription* packetDescs;
+    bool* bufferUsed;
+    int numberOfBuffersUsed;
+    
+    AudioQueueRef audioQueue;
+    AudioStreamBasicDescription currentAudioStreamBasicDescription;
+    
+    NSThread* playbackThread;
+    NSRunLoop* playbackThreadRunLoop;
+    NSConditionLock* threadFinishedCondLock;
+    
+    AudioFileStreamID audioFileStream;
+    
+    BOOL discontinuous;
+    
+    int bytesFilled;
+	int packetsFilled;
+    
+    int fillBufferIndex;
+    
+	UIBackgroundTaskIdentifier backgroundTaskId;
+	
+    AudioPlayerErrorCode errorCode;
+    AudioPlayerStopReason stopReason;
+    
+    int currentlyPlayingLock;
+    pthread_mutex_t playerMutex;
+    pthread_mutex_t queueBuffersMutex;
+    pthread_cond_t queueBufferReadyCondition;
+    
+    volatile BOOL waiting;
+    volatile BOOL disposeWasRequested;
+    volatile BOOL seekToTimeWasRequested;
+    volatile BOOL newFileToPlay;
+    volatile double requestedSeekTime;
+    volatile BOOL audioQueueFlushing;
+    volatile SInt64 audioPacketsReadCount;
+    volatile SInt64 audioPacketsPlayedCount;
+    
+    BOOL meteringEnabled;
+    AudioQueueLevelMeterState* levelMeterState;
+    NSInteger numberOfChannels;
+}
+
+@property (readonly) double duration;
+@property (readonly) double progress;
+@property (readwrite) AudioPlayerState state;
+@property (readonly) AudioPlayerStopReason stopReason;
+@property (readwrite, unsafe_unretained) id<AudioPlayerDelegate> delegate;
+@property (readwrite) BOOL meteringEnabled;
+
+-(id) init;
+-(id) initWithNumberOfAudioQueueBuffers:(int)numberOfAudioQueueBuffers andReadBufferSize:(int)readBufferSizeIn;
+-(DataSource*) dataSourceFromURL:(NSURL*)url;
+-(void) play:(NSURL*)url;
+-(void) queueDataSource:(DataSource*)dataSource withQueueItemId:(NSObject*)queueItemId;
+-(void) setDataSource:(DataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId;
+-(void) seekToTime:(double)value;
+-(void) pause;
+-(void) resume;
+-(void) stop;
+-(void) flushStop;
+-(void) mute;
+-(void) unmute;
+-(void) dispose;
+-(NSObject*) currentlyPlayingQueueItemId;
+-(void) updateMeters;
+-(float) peakPowerInDecibelsForChannel:(NSUInteger)channelNumber;
+-(float) averagePowerInDecibelsForChannel:(NSUInteger)channelNumber;
+
+@end

File diff suppressed because it is too large
+ 2279 - 0
AudioPlayer/AudioPlayer.m


+ 45 - 0
AudioPlayer/AutoRecoveringHttpDataSource.h

@@ -0,0 +1,45 @@
+/**********************************************************************************
+ AudioPlayer.m
+
+ Created by Thong Nguyen on 16/10/2012.
+ https://github.com/tumtumtum/audjustable
+
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "DataSource.h"
+#import "HttpDataSource.h"
+#import "DataSourceWrapper.h"
+
+@interface AutoRecoveringHttpDataSource : DataSourceWrapper
+
+-(id) initWithHttpDataSource:(HttpDataSource*)innerDataSource;
+
+@property (readonly) HttpDataSource* innerDataSource;
+
+@end

+ 236 - 0
AudioPlayer/AutoRecoveringHttpDataSource.m

@@ -0,0 +1,236 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 16/10/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen''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 <COPYRIGHT HOLDER> 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 <sys/socket.h>
+#import <netinet/in.h>
+#import <netinet6/in6.h>
+#import <arpa/inet.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+#import <Foundation/Foundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+#import "AutoRecoveringHttpDataSource.h"
+
+#define MAX_IMMEDIATE_RECONNECT_ATTEMPTS (8)
+#define MAX_ATTEMPTS_WITH_SERVER_ERROR (MAX_IMMEDIATE_RECONNECT_ATTEMPTS + 2)
+
+@interface AutoRecoveringHttpDataSource()
+{
+	int reconnectAttempts;
+    BOOL waitingForNetwork;
+    SCNetworkReachabilityRef reachabilityRef;
+}
+
+-(void) reachabilityChanged;
+
+@end
+
+static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
+{
+    @autoreleasepool
+    {
+        AutoRecoveringHttpDataSource* dataSource = (__bridge AutoRecoveringHttpDataSource*)info;
+        
+        [dataSource reachabilityChanged];
+    }
+}
+
+@implementation AutoRecoveringHttpDataSource
+
+-(HttpDataSource*) innerHttpDataSource
+{
+    return (HttpDataSource*)self.innerDataSource;
+}
+
+-(id) initWithHttpDataSource:(HttpDataSource*)innerDataSourceIn
+{
+    if (self = [super initWithDataSource:innerDataSourceIn])
+    {
+        self.innerDataSource.delegate = self;
+        
+        struct sockaddr_in zeroAddress;
+        
+        bzero(&zeroAddress, sizeof(zeroAddress));
+        zeroAddress.sin_len = sizeof(zeroAddress);
+        zeroAddress.sin_family = AF_INET;
+        
+        reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
+    }
+    
+    return self;
+}
+
+-(BOOL) startNotifierOnRunLoop:(NSRunLoop*)runLoop
+{
+    BOOL retVal = NO;
+    SCNetworkReachabilityContext context = { 0, (__bridge void*)self, NULL, NULL, NULL };
+    
+    if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context))
+    {
+		if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, runLoop.getCFRunLoop, kCFRunLoopDefaultMode))
+        {
+            retVal = YES;
+        }
+    }
+    
+    return retVal;
+}
+
+-(BOOL) registerForEvents:(NSRunLoop*)runLoop
+{
+    [super registerForEvents:runLoop];
+    [self startNotifierOnRunLoop:runLoop];
+    
+    return YES;
+}
+
+-(void) unregisterForEvents
+{
+    [self stopNotifier];
+}
+
+-(void) stopNotifier
+{
+    if (reachabilityRef != NULL)
+    {
+        SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
+        SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    }
+}
+
+-(BOOL) hasGotNetworkConnection
+{
+    SCNetworkReachabilityFlags flags;
+    
+    if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
+    {
+        return ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
+    }
+    
+    return NO;
+}
+
+-(void) dealloc
+{
+    self.innerDataSource.delegate = nil;
+    
+    [self stopNotifier];
+    
+    [NSObject cancelPreviousPerformRequestsWithTarget:self];
+    
+    if (reachabilityRef!= NULL)
+    {
+        CFRelease(reachabilityRef);
+    }
+}
+
+-(void) reachabilityChanged
+{
+    if (waitingForNetwork)
+    {
+        waitingForNetwork = NO;
+        
+        [self attemptReconnect];
+    }
+}
+
+-(void) dataSourceDataAvailable:(DataSource*)dataSource
+{
+    reconnectAttempts = 0;
+    
+    [super dataSourceDataAvailable:dataSource];
+}
+
+-(void) attemptReconnect
+{
+    reconnectAttempts++;
+    
+    [self seekToOffset:self.position];
+}
+
+-(void) processRetryOnError
+{
+    if (![self hasGotNetworkConnection])
+    {
+        waitingForNetwork = YES;
+        
+        return;
+    }
+    
+    if (!(self.innerDataSource.httpStatusCode >= 200 && self.innerDataSource.httpStatusCode <= 299) && reconnectAttempts >= MAX_ATTEMPTS_WITH_SERVER_ERROR)
+    {
+        [super dataSourceErrorOccured:self];
+    }
+    else if (reconnectAttempts > MAX_IMMEDIATE_RECONNECT_ATTEMPTS)
+    {
+        [self performSelector:@selector(attemptReconnect) withObject:nil afterDelay:5];
+    }
+    else
+    {
+        [self attemptReconnect];
+    }
+}
+
+-(void) dataSourceEof:(DataSource*)dataSource
+{
+    if ([self position] != [self length])
+    {
+        [self processRetryOnError];
+        
+        return;
+    }
+    
+    [self.delegate dataSourceEof:self];
+}
+
+-(void) dataSourceErrorOccured:(DataSource*)dataSource
+{
+    if (self.innerDataSource.httpStatusCode == 416 /* Range out of bounds */)
+    {
+        [super dataSourceEof:dataSource];
+    }
+    else
+    {
+        [self processRetryOnError];
+    }
+    
+}
+
+-(NSString*) description
+{
+    return [NSString stringWithFormat:@"Auto-recovering HTTP data source with file length: %lld and position: %lld", self.length, self.position];
+    
+}
+
+@end

+ 57 - 0
AudioPlayer/CoreFoundationDataSource.h

@@ -0,0 +1,57 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "DataSource.h"
+
+@class CoreFoundationDataSource;
+
+@interface CoreFoundationDataSourceClientInfo : NSObject
+@property (readwrite) CFReadStreamRef readStreamRef;
+@property (readwrite, retain) CoreFoundationDataSource* datasource;
+@end
+
+@interface CoreFoundationDataSource : DataSource
+{
+@protected
+    CFReadStreamRef stream;
+    NSRunLoop* eventsRunLoop;
+}
+
+-(BOOL) reregisterForEvents;
+
+-(void) dataAvailable;
+-(void) eof;
+-(void) errorOccured;
+
+@end

+ 161 - 0
AudioPlayer/CoreFoundationDataSource.m

@@ -0,0 +1,161 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "CoreFoundationDataSource.h"
+
+static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eventType, void* inClientInfo)
+{
+	CoreFoundationDataSource* datasource = (__bridge CoreFoundationDataSource*)inClientInfo;
+    
+    switch (eventType)
+    {
+        case kCFStreamEventErrorOccurred:
+            [datasource errorOccured];
+            break;
+        case kCFStreamEventEndEncountered:
+            [datasource eof];
+            break;
+        case kCFStreamEventHasBytesAvailable:
+            [datasource dataAvailable];
+            break;
+        default:
+            break;
+    }
+}
+
+@implementation CoreFoundationDataSourceClientInfo
+@synthesize readStreamRef, datasource;
+@end
+
+@implementation CoreFoundationDataSource
+
+-(void) dataAvailable
+{
+    [self.delegate dataSourceDataAvailable:self];
+}
+
+-(void) eof
+{
+    [self.delegate dataSourceEof:self];
+}
+
+-(void) errorOccured
+{
+    [self.delegate dataSourceErrorOccured:self];
+}
+
+-(void) dealloc
+{
+    if (stream)
+    {
+        [self unregisterForEvents];
+        
+        [self close];
+        
+        stream = 0;
+    }
+}
+
+-(void) close
+{
+    if (stream)
+    {
+        CFReadStreamClose(stream);
+        CFRelease(stream);
+        
+        stream = 0;
+    }
+}
+
+-(void) seekToOffset:(long long)offset
+{
+}
+
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
+{
+    return CFReadStreamRead(stream, buffer, size);
+}
+
+-(void) unregisterForEvents
+{
+    if (stream)
+    {
+        CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, NULL, NULL);
+        CFReadStreamUnscheduleFromRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
+    }
+}
+
+-(BOOL) reregisterForEvents
+{
+    if (eventsRunLoop && stream)
+    {
+        CFStreamClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
+        CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, ReadStreamCallbackProc, &context);
+        CFReadStreamScheduleWithRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
+        
+        return YES;
+    }
+    
+    return NO;
+}
+
+-(BOOL) registerForEvents:(NSRunLoop*)runLoop
+{
+    eventsRunLoop = runLoop;
+ 
+    if (stream)
+    {
+        CFStreamClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
+        
+        CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, ReadStreamCallbackProc, &context);
+        
+        CFReadStreamScheduleWithRunLoop(stream, [eventsRunLoop getCFRunLoop], kCFRunLoopCommonModes);
+    
+        return YES;
+    }
+    
+    return NO;
+}
+
+-(BOOL) hasBytesAvailable
+{
+    if (!stream)
+    {
+        return NO;
+    }
+    
+    return CFReadStreamHasBytesAvailable(stream);
+}
+
+@end

+ 66 - 0
AudioPlayer/DataSource.h

@@ -0,0 +1,66 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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>
+#include <AudioToolbox/AudioToolbox.h>
+
+@class DataSource;
+
+@protocol DataSourceDelegate<NSObject>
+-(void) dataSourceDataAvailable:(DataSource*)dataSource;
+-(void) dataSourceErrorOccured:(DataSource*)dataSource;
+-(void) dataSourceEof:(DataSource*)dataSource;
+@end
+
+@protocol AudioDataSource<NSObject>
+@property (readwrite) double averageBitRate;
+@property (readwrite) long long audioDataOffset;
+@end
+
+@interface DataSource : NSObject
+
+@property (readonly) long long position;
+@property (readonly) long long length;
+@property (readonly) BOOL hasBytesAvailable;
+@property (readwrite, unsafe_unretained) id<DataSourceDelegate> delegate;
+
+-(BOOL) registerForEvents:(NSRunLoop*)runLoop;
+-(void) unregisterForEvents;
+-(void) close;
+
+-(void) seekToOffset:(long long)offset;
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size;
+-(AudioFileTypeID) audioFileTypeHint;
+
+@end

+ 82 - 0
AudioPlayer/DataSource.m

@@ -0,0 +1,82 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "DataSource.h"
+
+@implementation DataSource
+@synthesize delegate;
+
+-(long long) length
+{
+    return 0;
+}
+
+-(void) seekToOffset:(long long)offset
+{
+}
+
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
+{
+    return -1;
+}
+
+-(long long) position
+{
+    return 0;
+}
+
+-(BOOL) registerForEvents:(NSRunLoop*)runLoop
+{
+    return NO;
+}
+
+-(void) unregisterForEvents
+{
+}
+
+-(void) close
+{
+}
+
+-(BOOL) hasBytesAvailable
+{
+    return NO;
+}
+
+-(AudioFileTypeID) audioFileTypeHint
+{
+    return 0;
+}
+
+@end

+ 43 - 0
AudioPlayer/DataSourceWrapper.h

@@ -0,0 +1,43 @@
+/**********************************************************************************
+ AudioPlayer.m
+
+ Created by Thong Nguyen on 16/10/2012.
+ https://github.com/tumtumtum/audjustable
+
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen''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 <COPYRIGHT HOLDER> 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 "DataSource.h"
+
+@interface DataSourceWrapper : DataSource<DataSourceDelegate>
+
+-(id) initWithDataSource:(DataSource*)innerDataSource;
+
+@property (readonly) DataSource* innerDataSource;
+
+@end

+ 120 - 0
AudioPlayer/DataSourceWrapper.m

@@ -0,0 +1,120 @@
+/**********************************************************************************
+ AudioPlayer.m
+
+ Created by Thong Nguyen on 16/10/2012.
+ https://github.com/tumtumtum/audjustable
+
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen''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 <COPYRIGHT HOLDER> 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 "DataSourceWrapper.h"
+
+@interface DataSourceWrapper()
+@property (readwrite) DataSource* innerDataSource;
+@end
+
+@implementation DataSourceWrapper
+
+-(id) initWithDataSource:(DataSource*)innerDataSourceIn
+{
+    if (self = [super init])
+    {
+        self.innerDataSource = innerDataSourceIn;
+        
+        self.innerDataSource.delegate = self;
+    }
+    
+    return self;
+}
+
+-(AudioFileTypeID) audioFileTypeHint
+{
+    return self.innerDataSource.audioFileTypeHint;
+}
+
+-(void) dealloc
+{
+    self.innerDataSource.delegate = nil;
+}
+
+-(long long) length
+{
+    return self.innerDataSource.length;
+}
+
+-(void) seekToOffset:(long long)offset
+{
+    return [self.innerDataSource seekToOffset:offset];
+}
+
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
+{
+    return [self.innerDataSource readIntoBuffer:buffer withSize:size];
+}
+
+-(long long) position
+{
+    return self.innerDataSource.position;
+}
+
+-(BOOL) registerForEvents:(NSRunLoop*)runLoop
+{
+    return [self.innerDataSource registerForEvents:runLoop];
+}
+
+-(void) unregisterForEvents
+{
+    [self.innerDataSource unregisterForEvents];
+}
+
+-(void) close
+{
+    [self.innerDataSource close];
+}
+
+-(BOOL) hasBytesAvailable
+{
+    return self.innerDataSource.hasBytesAvailable;
+}
+
+-(void) dataSourceDataAvailable:(DataSource*)dataSource
+{
+    [self.delegate dataSourceDataAvailable:self];
+}
+
+-(void) dataSourceErrorOccured:(DataSource*)dataSource
+{
+    [self.delegate dataSourceErrorOccured:self];
+}
+
+-(void) dataSourceEof:(DataSource*)dataSource
+{
+    [self.delegate dataSourceEof:self];
+}
+
+@end

+ 54 - 0
AudioPlayer/HttpDataSource.h

@@ -0,0 +1,54 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "CoreFoundationDataSource.h"
+
+@interface HttpDataSource : CoreFoundationDataSource
+{
+@private
+    int seekStart;
+    int relativePosition;
+    int fileLength;
+    int discontinuous;
+    NSDictionary* httpHeaders;
+    AudioFileTypeID audioFileTypeHint;
+}
+
+@property (readwrite, retain) NSURL* url;
+@property (readwrite) UInt32 httpStatusCode;
+
++(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
+-(id) initWithURL:(NSURL*)url;
+
+@end

+ 252 - 0
AudioPlayer/HttpDataSource.m

@@ -0,0 +1,252 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "HttpDataSource.h"
+#import "LocalFileDataSource.h"
+
+@interface HttpDataSource()
+-(void) open;
+@end
+
+@implementation HttpDataSource
+@synthesize url;
+
+-(id) initWithURL:(NSURL*)urlIn
+{
+    if (self = [super init])
+    {
+        seekStart = 0;
+        relativePosition = 0;
+        fileLength = -1;
+        
+        self.url = urlIn;
+        
+        [self open];
+        
+        audioFileTypeHint = [LocalFileDataSource audioFileTypeHintFromFileExtension:urlIn.pathExtension];
+    }
+    
+    return self;
+}
+
++(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)mimeType
+{
+    static dispatch_once_t onceToken;
+    static NSDictionary* fileTypesByMimeType;
+    
+    dispatch_once(&onceToken, ^
+                  {
+                      fileTypesByMimeType =
+                      @{
+                        @"audio/mp3": @(kAudioFileMP3Type),
+                        @"audio/mpg": @(kAudioFileMP3Type),
+                        @"audio/mpeg": @(kAudioFileMP3Type),
+                        @"audio/wav": @(kAudioFileWAVEType),
+                        @"audio/aifc": @(kAudioFileAIFCType),
+                        @"audio/aiff": @(kAudioFileAIFFType),
+                        @"audio/x-m4a": @(kAudioFileM4AType),
+                        @"audio/x-mp4": @(kAudioFileMPEG4Type),
+                        @"audio/m4a": @(kAudioFileM4AType),
+                        @"audio/mp4": @(kAudioFileMPEG4Type),
+                        @"audio/caf": @(kAudioFileCAFType),
+                        @"audio/aac": @(kAudioFileAAC_ADTSType),
+                        @"audio/ac3": @(kAudioFileAC3Type),
+                        @"audio/3gp": @(kAudioFile3GPType)
+                        };
+                  });
+    
+    NSNumber* number = [fileTypesByMimeType objectForKey:mimeType];
+    
+    if (!number)
+    {
+        return 0;
+    }
+    
+    return (AudioFileTypeID)number.intValue;
+}
+
+-(AudioFileTypeID) audioFileTypeHint
+{
+    return audioFileTypeHint;
+}
+
+-(void) dataAvailable
+{
+    if (fileLength < 0)
+    {
+        CFTypeRef response = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);
+        
+        httpHeaders = (__bridge_transfer NSDictionary*)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)response);
+        
+        self.httpStatusCode = CFHTTPMessageGetResponseStatusCode((CFHTTPMessageRef)response);
+        
+        CFRelease(response);
+        
+        if (self.httpStatusCode == 200)
+        {
+            if (seekStart == 0)
+            {
+                fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue];
+            }
+            
+            NSString* contentType = [httpHeaders objectForKey:@"Content-Type"];
+            AudioFileTypeID typeIdFromMimeType = [HttpDataSource audioFileTypeHintFromMimeType:contentType];
+            
+            if (typeIdFromMimeType != 0)
+            {
+                audioFileTypeHint = typeIdFromMimeType;
+            }
+        }
+        else
+        {
+            [self errorOccured];
+            
+            return;
+        }
+    }
+    
+    [super dataAvailable];
+}
+
+-(long long) position
+{
+    return seekStart + relativePosition;
+}
+
+-(long long) length
+{
+    return fileLength >= 0 ? fileLength : 0;
+}
+
+-(void) seekToOffset:(long long)offset
+{
+    if (eventsRunLoop)
+    {
+        [self unregisterForEvents];
+    }
+    
+    if (stream)
+    {
+        CFReadStreamClose(stream);
+        CFRelease(stream);
+    }
+    
+    stream = 0;
+    relativePosition = 0;
+    seekStart = (int)offset;
+    
+    [self open];
+    [self reregisterForEvents];
+}
+
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
+{
+    if (size == 0)
+    {
+        return 0;
+    }
+    
+    int read = CFReadStreamRead(stream, buffer, size);
+    
+    if (read < 0)
+    {
+        return read;
+    }
+    
+    relativePosition += read;
+    
+    return read;
+}
+
+-(void) open
+{
+    CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (__bridge CFURLRef)self.url, kCFHTTPVersion1_1);
+    
+    if (seekStart > 0)
+    {
+        CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), (__bridge CFStringRef)[NSString stringWithFormat:@"bytes=%d-", seekStart]);
+        
+        discontinuous = YES;
+    }
+    
+    stream = CFReadStreamCreateForHTTPRequest(NULL, message);
+    
+	if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
+    {
+        CFRelease(message);
+        
+        return;
+    }
+    
+    // Proxy support
+    
+    CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
+    CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings);
+    CFRelease(proxySettings);
+    
+    // SSL support
+    
+    if ([url.scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)
+    {
+        NSDictionary* sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
+                                     (NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel,
+                                     [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
+                                     [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots,
+                                     [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
+                                     [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
+                                     [NSNull null], kCFStreamSSLPeerName,
+                                     nil];
+        
+        CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, (__bridge CFTypeRef)sslSettings);
+    }
+    
+    // Open
+    
+    if (!CFReadStreamOpen(stream))
+    {
+        CFRelease(stream);
+        CFRelease(message);
+        
+        return;
+    }
+    
+    CFRelease(message);
+}
+
+- (NSString *)description
+{
+    return [NSString stringWithFormat:@"HTTP data source with file length: %lld and position: %lld", self.length, self.position];
+}
+
+@end

+ 51 - 0
AudioPlayer/LocalFileDataSource.h

@@ -0,0 +1,51 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "CoreFoundationDataSource.h"
+
+@interface LocalFileDataSource : CoreFoundationDataSource
+{
+@private
+    long long position;
+    long long length;
+    AudioFileTypeID audioFileTypeHint;
+}
+
++(AudioFileTypeID) audioFileTypeHintFromFileExtension:(NSString*)fileExtension;
+
+@property (readonly, copy) NSString* filePath;
+
+-(id) initWithFilePath:(NSString*)filePath;
+
+@end

+ 214 - 0
AudioPlayer/LocalFileDataSource.m

@@ -0,0 +1,214 @@
+/**********************************************************************************
+ AudioPlayer.m
+ 
+ Created by Thong Nguyen on 14/05/2012.
+ https://github.com/tumtumtum/audjustable
+ 
+ Copyright (c) 2012 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 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. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
+ 4. Neither the name of Thong Nguyen nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''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 THONG NGUYEN 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 "LocalFileDataSource.h"
+
+@interface LocalFileDataSource()
+@property (readwrite, copy) NSString* filePath;
+
+-(void) open;
+@end
+
+@implementation LocalFileDataSource
+@synthesize filePath;
+
+-(id) initWithFilePath:(NSString*)filePathIn
+{
+    if (self = [super init])
+    {
+        self.filePath = filePathIn;
+        
+        [self open];
+        
+        audioFileTypeHint = [LocalFileDataSource audioFileTypeHintFromFileExtension:filePathIn.pathExtension];
+    }
+    
+    return self;
+}
+
++(AudioFileTypeID) audioFileTypeHintFromFileExtension:(NSString*)fileExtension
+{
+    static dispatch_once_t onceToken;
+    static NSDictionary* fileTypesByFileExtensions;
+    
+    dispatch_once(&onceToken, ^
+    {
+        fileTypesByFileExtensions =
+        @{
+            @"mp3": @(kAudioFileMP3Type),
+            @"wav": @(kAudioFileWAVEType),
+            @"aifc": @(kAudioFileAIFCType),
+            @"aiff": @(kAudioFileAIFFType),
+            @"m4a": @(kAudioFileM4AType),
+            @"mp4": @(kAudioFileMPEG4Type),
+            @"caf": @(kAudioFileCAFType),
+            @"aac": @(kAudioFileAAC_ADTSType),
+            @"ac3": @(kAudioFileAC3Type),
+            @"3gp": @(kAudioFile3GPType)
+        };
+    });
+    
+    NSNumber* number = [fileTypesByFileExtensions objectForKey:fileExtension];
+    
+    if (!number)
+    {
+        return 0;
+    }
+    
+    return (AudioFileTypeID)number.intValue;
+}
+
+-(AudioFileTypeID) audioFileTypeHint
+{
+    return audioFileTypeHint;
+}
+
+-(void) dealloc
+{
+    [self close];
+}
+
+-(void) close
+{
+    if (stream)
+    {
+        CFReadStreamClose(stream);
+        
+        stream = 0;
+    }
+}
+
+-(void) open
+{
+    NSURL* url = [[NSURL alloc] initFileURLWithPath:self.filePath];
+    
+    if (stream)
+    {
+        CFReadStreamClose(stream);
+        CFRelease(stream);
+        
+        stream = 0;
+    }
+    
+    stream = CFReadStreamCreateWithFile(NULL, (__bridge CFURLRef)url);
+    
+    SInt32 errorCode;
+    
+    NSNumber* number = (__bridge_transfer NSNumber*)CFURLCreatePropertyFromResource(NULL, (__bridge CFURLRef)url, kCFURLFileLength, &errorCode);
+    
+    if (number)
+    {
+        length = number.longLongValue;
+    }
+    
+    CFReadStreamOpen(stream);
+}
+
+-(long long) position
+{
+    return position;
+}
+
+-(long long) length
+{
+    return length;
+}
+
+-(int) readIntoBuffer:(UInt8*)buffer withSize:(int)size
+{
+    int retval = CFReadStreamRead(stream, buffer, size);
+
+    if (retval > 0)
+    {
+        position += retval;
+    }
+    else
+    {
+        NSNumber* property = (__bridge_transfer NSNumber*)CFReadStreamCopyProperty(stream, kCFStreamPropertyFileCurrentOffset);
+        
+        position = property.longLongValue;
+    }
+    
+    return retval;
+}
+
+-(void) seekToOffset:(long long)offset
+{
+    CFStreamStatus status = kCFStreamStatusClosed;
+    
+    if (stream != 0)
+    {
+		status = CFReadStreamGetStatus(stream);
+    }
+    
+    BOOL reopened = NO;
+    
+    if (status == kCFStreamStatusAtEnd || status == kCFStreamStatusClosed || status == kCFStreamStatusError)
+    {
+        reopened = YES;
+        
+        [self close];        
+        [self open];
+        [self reregisterForEvents];
+    }
+    
+    if (CFReadStreamSetProperty(stream, kCFStreamPropertyFileCurrentOffset, (__bridge CFTypeRef)[NSNumber numberWithLongLong:offset]) != TRUE)
+    {
+        position = 0;
+    }
+    else
+    {
+        position = offset;
+    }
+    
+    if (!reopened)
+    {
+        CFRunLoopPerformBlock(eventsRunLoop.getCFRunLoop, NSRunLoopCommonModes, ^
+        {
+            if ([self hasBytesAvailable])
+            {
+                [self dataAvailable];
+            }
+        });
+        
+        CFRunLoopWakeUp(eventsRunLoop.getCFRunLoop);
+    }
+}
+
+-(NSString*) description
+{
+    return self->filePath;
+}
+
+@end

+ 27 - 0
DropDownChooseProtocol.h

@@ -0,0 +1,27 @@
+//
+//  DropDownChooseProtocol.h
+//  DropDownDemo
+//
+//  Created by 童明城 on 14-5-28.
+//  Copyright (c) 2016年 童明城. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol DropDownChooseDelegate <NSObject>
+
+@optional
+
+-(void) chooseAtSection:(NSInteger)section index:(NSInteger)index;
+@end
+
+@protocol DropDownChooseDataSource <NSObject>
+-(NSInteger)numberOfSections;
+-(NSInteger)numberOfRowsInSection:(NSInteger)section;
+-(NSString *)titleInSection:(NSInteger)section index:(NSInteger) index;
+-(NSInteger)defaultShowSection:(NSInteger)section;
+
+@end
+
+
+

+ 31 - 0
DropDownListView.h

@@ -0,0 +1,31 @@
+//
+//  DropDownListView.h
+//  DropDownDemo
+//
+//  Created by 童明城 on 14-5-28.
+//  Copyright (c) 2016年 童明城. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "DropDownChooseProtocol.h"
+
+#define SECTION_BTN_TAG_BEGIN   1000
+#define SECTION_IV_TAG_BEGIN    3000
+@interface DropDownListView : UIView<UITableViewDelegate,UITableViewDataSource>{
+    NSInteger currentExtendSection;     //当前展开的section ,默认-1时,表示都没有展开
+}
+
+@property (nonatomic, assign) id<DropDownChooseDelegate> dropDownDelegate;
+@property (nonatomic, assign) id<DropDownChooseDataSource> dropDownDataSource;
+
+@property (nonatomic, strong) UIView *mSuperView;
+@property (nonatomic, strong) UIView *mTableBaseView;
+@property (nonatomic, strong) UITableView *mTableView;
+
+- (id)initWithFrame:(CGRect)frame dataSource:(id)datasource delegate:(id) delegate;
+- (void)setTitle:(NSString *)title inSection:(NSInteger) section;
+
+- (BOOL)isShow;
+- (void)hideExtendedChooseView;
+
+@end

+ 239 - 0
DropDownListView.m

@@ -0,0 +1,239 @@
+//
+//  DropDownListView.m
+//  DropDownDemo
+//
+//  Created by 童明城 on 14-5-28.
+//  Copyright (c) 2016年 童明城. All rights reserved.
+//
+
+#import "DropDownListView.h"
+#import "StyledTableViewCell.h"
+
+#define DEGREES_TO_RADIANS(angle) ((angle)/180.0 *M_PI)
+#define RADIANS_TO_DEGREES(radians) ((radians)*(180.0/M_PI))
+
+@implementation DropDownListView
+
+- (id)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        // Initialization code
+    }
+    return self;
+}
+
+- (id)initWithFrame:(CGRect)frame dataSource:(id)datasource delegate:(id) delegate
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [UIColor whiteColor];
+        currentExtendSection = -1;
+        self.dropDownDataSource = datasource;
+        self.dropDownDelegate = delegate;
+        
+        NSInteger sectionNum =0;
+        if ([self.dropDownDataSource respondsToSelector:@selector(numberOfSections)] ) {
+            
+            sectionNum = [self.dropDownDataSource numberOfSections];
+        }
+        
+        if (sectionNum == 0) {
+            self = nil;
+        }
+        
+        //初始化默认显示view
+        CGFloat sectionWidth = (1.0*(frame.size.width)/sectionNum);
+        for (int i = 0; i <sectionNum; i++) {
+            UIButton *sectionBtn = [[UIButton alloc] initWithFrame:CGRectMake(sectionWidth*i, 1, sectionWidth, frame.size.height-2)];
+            sectionBtn.tag = SECTION_BTN_TAG_BEGIN + i;
+            [sectionBtn addTarget:self action:@selector(sectionBtnTouch:) forControlEvents:UIControlEventTouchUpInside];
+            NSString *sectionBtnTitle = @"--";
+            if ([self.dropDownDataSource respondsToSelector:@selector(titleInSection:index:)]) {
+                sectionBtnTitle = [self.dropDownDataSource titleInSection:i index:[self.dropDownDataSource defaultShowSection:i]];
+            }
+            [sectionBtn  setTitle:sectionBtnTitle forState:UIControlStateNormal];
+            [sectionBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
+            sectionBtn.titleLabel.font = [UIFont boldSystemFontOfSize:14.0f];
+            [self addSubview:sectionBtn];
+            
+            UIImageView *sectionBtnIv = [[UIImageView alloc] initWithFrame:CGRectMake(sectionWidth*i +(sectionWidth - 16), (self.frame.size.height-12)/2, 12, 12)];
+            [sectionBtnIv setImage:[UIImage imageNamed:@"down_dark.png"]];
+            [sectionBtnIv setContentMode:UIViewContentModeScaleToFill];
+            sectionBtnIv.tag = SECTION_IV_TAG_BEGIN + i;
+            
+            [self addSubview: sectionBtnIv];
+            
+            if (i<sectionNum && i != 0) {
+                UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(sectionWidth*i, frame.size.height/4, 1, frame.size.height/2)];
+                lineView.backgroundColor = [UIColor lightGrayColor];
+                [self addSubview:lineView];
+            }
+            
+        }
+        
+    }
+    return self;
+}
+
+-(void)sectionBtnTouch:(UIButton *)btn
+{
+    NSInteger section = btn.tag - SECTION_BTN_TAG_BEGIN;
+    
+    UIImageView *currentIV= (UIImageView *)[self viewWithTag:(SECTION_IV_TAG_BEGIN +currentExtendSection)];
+    
+    [UIView animateWithDuration:0.3 animations:^{
+        currentIV.transform = CGAffineTransformRotate(currentIV.transform, DEGREES_TO_RADIANS(180));
+    }];
+    
+    if (currentExtendSection == section) {
+        [self hideExtendedChooseView];
+    }else{
+        currentExtendSection = section;
+        currentIV = (UIImageView *)[self viewWithTag:SECTION_IV_TAG_BEGIN + currentExtendSection];
+        [UIView animateWithDuration:0.3 animations:^{
+            currentIV.transform = CGAffineTransformRotate(currentIV.transform, DEGREES_TO_RADIANS(180));
+        }];
+        
+        [self showChooseListViewInSection:currentExtendSection choosedIndex:[self.dropDownDataSource defaultShowSection:currentExtendSection]];
+    }
+
+}
+
+- (void)setTitle:(NSString *)title inSection:(NSInteger) section
+{
+    UIButton *btn = (id)[self viewWithTag:SECTION_BTN_TAG_BEGIN +section];
+    [btn setTitle:title forState:UIControlStateNormal];
+}
+
+- (BOOL)isShow
+{
+    if (currentExtendSection == -1) {
+        return NO;
+    }
+    return YES;
+}
+-  (void)hideExtendedChooseView
+{
+    if (currentExtendSection != -1) {
+        currentExtendSection = -1;
+        CGRect rect = self.mTableView.frame;
+        rect.size.height = 0;
+        [UIView animateWithDuration:0.3 animations:^{
+            self.mTableBaseView.alpha = 1.0f;
+            self.mTableView.alpha = 1.0f;
+            
+            self.mTableBaseView.alpha = 0.2f;
+            self.mTableView.alpha = 0.2;
+            
+            self.mTableView.frame = rect;
+        }completion:^(BOOL finished) {
+            [self.mTableView removeFromSuperview];
+            [self.mTableBaseView removeFromSuperview];
+        }];
+    }
+}
+
+-(void)showChooseListViewInSection:(NSInteger)section choosedIndex:(NSInteger)index
+{
+    if (!self.mTableView) {
+        self.mTableBaseView = [[UIView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y + self.frame.size.height , self.frame.size.width, self.mSuperView.frame.size.height - self.frame.origin.y - self.frame.size.height)];
+        self.mTableBaseView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.5];
+        
+        UITapGestureRecognizer *bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(bgTappedAction:)];
+        [self.mTableBaseView addGestureRecognizer:bgTap];
+        
+        self.mTableView = [[UITableView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y + self.frame.size.height, self.frame.size.width, 240) style:UITableViewStylePlain];
+        self.mTableView.delegate = self;
+        self.mTableView.dataSource = self;
+        
+    }
+    
+    //修改tableview的frame
+    int sectionWidth = (self.frame.size.width)/[self.dropDownDataSource numberOfSections];
+    CGRect rect = self.mTableView.frame;
+    rect.origin.x = sectionWidth *section;
+    rect.size.width = sectionWidth;
+    rect.size.height = 0;
+    self.mTableView.frame = rect;
+    [self.mSuperView addSubview:self.mTableBaseView];
+    [self.mSuperView addSubview:self.mTableView];
+    
+    //动画设置位置
+    rect .size.height = 240;
+    [UIView animateWithDuration:0.3 animations:^{
+        self.mTableBaseView.alpha = 0.2;
+        self.mTableView.alpha = 0.2;
+        
+        self.mTableBaseView.alpha = 1.0;
+        self.mTableView.alpha = 1.0;
+        self.mTableView.frame =  rect;
+    }];
+    [self.mTableView reloadData];
+}
+
+-(void)bgTappedAction:(UITapGestureRecognizer *)tap
+{
+    UIImageView *currentIV = (UIImageView *)[self viewWithTag:(SECTION_IV_TAG_BEGIN + currentExtendSection)];
+    [UIView animateWithDuration:0.3 animations:^{
+        currentIV.transform = CGAffineTransformRotate(currentIV.transform, DEGREES_TO_RADIANS(180));
+    }];
+    [self hideExtendedChooseView];
+}
+#pragma mark -- UITableView Delegate
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return 40;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    if ([self.dropDownDelegate respondsToSelector:@selector(chooseAtSection:index:)]) {
+        NSString *chooseCellTitle = [self.dropDownDataSource titleInSection:currentExtendSection index:indexPath.row];
+        
+        UIButton *currentSectionBtn = (UIButton *)[self viewWithTag:SECTION_BTN_TAG_BEGIN + currentExtendSection];
+        [currentSectionBtn setTitle:chooseCellTitle forState:UIControlStateNormal];
+        
+        [self.dropDownDelegate chooseAtSection:currentExtendSection index:indexPath.row];
+        [self hideExtendedChooseView];
+    }
+}
+
+#pragma mark -- UITableView DataSource
+
+
+-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    return 1;
+}
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    return [self.dropDownDataSource numberOfRowsInSection:currentExtendSection];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    static NSString * cellIdentifier = @"cellIdentifier";
+    StyledTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+    if (!cell) {
+        cell = [[StyledTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
+        cell.accessoryType = UITableViewCellAccessoryNone;
+    }
+    
+    cell.textLabel.text = [self.dropDownDataSource titleInSection:currentExtendSection index:indexPath.row];
+    cell.textLabel.font = [UIFont systemFontOfSize:14];
+    return cell;
+}
+
+
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect
+{
+    // Drawing code
+}
+*/
+
+@end

+ 72 - 0
FMDB2_1/DBDaoHelper.h

@@ -0,0 +1,72 @@
+//
+//  DBDaoHelper.h
+//  SaintiFrameWork
+//
+//  Created by yan.jm on 14/11/6.
+//  Copyright (c) 2016年 yan.jm. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FMSingerModel.h"
+#import "FMSongListModel.h"
+@interface DBDaoHelper : NSObject
+
+/*
+ 方法说明:
+ 创建数据库表,这个方法一般在启动程序时调用。appdelegate中调用
+ 
+ 参数说明:
+ <#参数说明#>
+ 
+ 返回结果:
+ <#返回结果#>
+ */
++(BOOL)createAllTable;
++(NSMutableArray *)selectMusicWithType:(NSInteger)type;
+/*
+ 方法说明:
+ 插入
+ 
+ 参数说明:
+ <#参数说明#>
+ 
+ 返回结果:
+ <#返回结果#>
+ */
++(BOOL)insertMusicWith:(NSInteger )songid type:(NSInteger)type name:(NSString *)name;
++(BOOL)insertMusicWith:(FMSongListModel * )model type:(NSInteger)type;
++(NSMutableArray *)selectDownMusicWithType:(NSInteger)type;
++(NSMutableArray *)selectDownMusicWithName:(NSString *)name;
+/*
+ 方法说明:
+ 插入
+ 
+ 参数说明:
+ <#参数说明#>
+ 
+ 返回结果:
+ <#返回结果#>
+ */
++(BOOL)insertDownMusicWith:(NSInteger )songid type:(NSInteger)type name:(NSString *)name;
+
+/*
+ 方法说明:
+ 查询
+ 
+ 参数说明:
+ <#参数说明#>
+ 
+ 返回结果:
+ <#返回结果#>
+ */
+//根据id查询便签信息
++(NSMutableArray *)selecXinqing;
++(BOOL)insertXinqingWith:(NSString *)time content:(NSString *)content;
++(BOOL)insertDownMusicWith:(FMSongListModel * )model type:(NSInteger)type;
++(BOOL)insertMyTypeWith:(NSString *)type;
++(NSMutableArray *)myType;
++(BOOL)DeleteMyTypeWith:(NSString *)type;
+//根据id查询便签信息
++(NSMutableArray *)myTypeId;
++(BOOL)DeleteMusicWith:(NSString *)type song_id:(NSString *)song_id;
+@end

File diff suppressed because it is too large
+ 331 - 0
FMDB2_1/DBDaoHelper.m


+ 13 - 0
FMDB2_1/DBHelper.h

@@ -0,0 +1,13 @@
+//
+//  DBHelper.h
+//  ZXFrameWork
+//
+//  Created by 智享 on 14-8-18.
+//  Copyright (c) 2016年 智享. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FMDatabase.h"
+@interface DBHelper : NSObject
++(FMDatabase *)openDatabase;
+@end

+ 31 - 0
FMDB2_1/DBHelper.m

@@ -0,0 +1,31 @@
+//
+//  DBHelper.m
+//  ZXFrameWork
+//
+//  Created by 智享 on 14-8-18.
+//  Copyright (c) 2016年 智享. All rights reserved.
+//
+
+#import "DBHelper.h"
+#import "LKDBUtils.h"
+@implementation DBHelper
++(FMDatabase *)openDatabase{
+    //paths: ios下Document路径,Document为ios中可读写的文件夹
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *documentDirectory = [paths objectAtIndex:0];
+    NSLog(@"%@",documentDirectory);
+    //dbPath: 数据库路径,在Document中。
+    NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@"FreeMusic.db"];
+    
+//    NSString *dbPath = [[NSBundle mainBundle]pathForResource:@"Test" ofType:@"db"];
+    
+    //创建数据库实例 db  这里说明下:如果路径中不存在"Test.db"的文件,sqlite会自动创建"Test.db"
+    FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;
+    if (![db open]) {
+        NSLog(@"Could not open db.");
+        return nil;
+    }else{
+        return db;
+    }
+}
+@end

+ 218 - 0
FMDB2_1/FMDatabase.h

@@ -0,0 +1,218 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+#import "FMResultSet.h"
+#import "FMDatabasePool.h"
+
+
+#if ! __has_feature(objc_arc)
+    #define FMDBAutorelease(__v) ([__v autorelease]);
+    #define FMDBReturnAutoreleased FMDBAutorelease
+
+    #define FMDBRetain(__v) ([__v retain]);
+    #define FMDBReturnRetained FMDBRetain
+
+    #define FMDBRelease(__v) ([__v release]);
+
+	#define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
+#else
+    // -fobjc-arc
+    #define FMDBAutorelease(__v)
+    #define FMDBReturnAutoreleased(__v) (__v)
+
+    #define FMDBRetain(__v)
+    #define FMDBReturnRetained(__v) (__v)
+
+    #define FMDBRelease(__v)
+
+	#if TARGET_OS_IPHONE
+		// Compiling for iOS
+		#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
+			// iOS 6.0 or later
+			#define FMDBDispatchQueueRelease(__v)
+		#else
+			// iOS 5.X or earlier
+			#define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
+		#endif
+	#else
+		// Compiling for Mac OS X
+		#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080     
+			// Mac OS X 10.8 or later
+			#define FMDBDispatchQueueRelease(__v)
+		#else
+			// Mac OS X 10.7 or earlier
+			#define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
+		#endif
+	#endif
+#endif
+
+
+@interface FMDatabase : NSObject  {
+    
+    sqlite3*            _db;
+    NSString*           _databasePath;
+    BOOL                _logsErrors;
+    BOOL                _crashOnErrors;
+    BOOL                _traceExecution;
+    BOOL                _checkedOut;
+    BOOL                _shouldCacheStatements;
+    BOOL                _isExecutingStatement;
+    BOOL                _inTransaction;
+    int                 _busyRetryTimeout;
+    
+    NSMutableDictionary *_cachedStatements;
+    NSMutableSet        *_openResultSets;
+    NSMutableSet        *_openFunctions;
+
+    NSDateFormatter     *_dateFormat;
+}
+
+
+@property (atomic, assign) BOOL traceExecution;
+@property (atomic, assign) BOOL checkedOut;
+@property (atomic, assign) int busyRetryTimeout;
+@property (atomic, assign) BOOL crashOnErrors;
+@property (atomic, assign) BOOL logsErrors;
+@property (atomic, retain) NSMutableDictionary *cachedStatements;
+
++ (id)databaseWithPath:(NSString*)inPath;
+- (id)initWithPath:(NSString*)inPath;
+
+- (BOOL)open;
+#if SQLITE_VERSION_NUMBER >= 3005000
+- (BOOL)openWithFlags:(int)flags;
+#endif
+- (BOOL)close;
+- (BOOL)goodConnection;
+- (void)clearCachedStatements;
+- (void)closeOpenResultSets;
+- (BOOL)hasOpenResultSets;
+
+// encryption methods.  You need to have purchased the sqlite encryption extensions for these to work.
+- (BOOL)setKey:(NSString*)key;
+- (BOOL)rekey:(NSString*)key;
+- (BOOL)setKeyWithData:(NSData *)keyData;
+- (BOOL)rekeyWithData:(NSData *)keyData;
+
+- (NSString *)databasePath;
+
+- (NSString*)lastErrorMessage;
+
+- (int)lastErrorCode;
+- (BOOL)hadError;
+- (NSError*)lastError;
+
+- (sqlite_int64)lastInsertRowId;
+
+- (sqlite3*)sqliteHandle;
+
+- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...;
+- (BOOL)executeUpdate:(NSString*)sql, ...;
+- (BOOL)executeUpdateWithFormat:(NSString *)format, ...;
+- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
+- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (FMResultSet *)executeQuery:(NSString*)sql, ...;
+- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...;
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
+- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (BOOL)rollback;
+- (BOOL)commit;
+- (BOOL)beginTransaction;
+- (BOOL)beginDeferredTransaction;
+- (BOOL)inTransaction;
+- (BOOL)shouldCacheStatements;
+- (void)setShouldCacheStatements:(BOOL)value;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (NSError*)inSavePoint:(void (^)(BOOL *rollback))block;
+#endif
+
++ (BOOL)isSQLiteThreadSafe;
++ (NSString*)sqliteLibVersion;
+
+- (int)changes;
+
+- (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block;
+
+
+
+/** Generate an NSDateFormat that won't be broken by timezone or locale changes.
+ 
+ Use this method to generate values to set the dateFormat property.
+ 
+ @param dateFormat A valid NSDateFormatter format string.
+ 
+ Example:
+ 
+ myDB.dateFormat = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+ 
+ Note that NSDateFormatter is not thread-safe, so the formatter generated by this method should be assigned to only one FMDB instance and should not be used for other purposes.
+ 
+ */
++ (NSDateFormatter *)storeableDateFormat:(NSString *)format;
+
+
+/** Test whether the database has a date formatter assigned.
+ 
+ */
+- (BOOL)hasDateFormatter;
+
+
+/** Set to a date formatter to use string dates with sqlite instead of the default UNIX timestamps.
+ 
+ Set to nil to use UNIX timestamps.
+ 
+ Defaults to nil.
+ 
+ Should be set using a formatter generated using FMDatabase::storeableDateFormat.
+ 
+ Note there is no direct getter for the NSDateFormatter, and you should not use the formatter you pass to FMDB for other purposes, as NSDateFormatter is not thread-safe.
+ 
+ */
+- (void)setDateFormat:(NSDateFormatter *)format;
+
+
+/** Convert the supplied NSString to NSDate, using the current database formatter.
+ 
+ Returns nil if no formatter is set.
+ 
+ */
+- (NSDate *)dateFromString:(NSString *)s;
+
+/** Convert the supplied NSDate to NSString, using the current database formatter.
+ 
+ Returns nil if no formatter is set.
+ 
+ */
+- (NSString *)stringFromDate:(NSDate *)date;
+
+
+
+
+
+
+
+
+
+
+@end
+
+@interface FMStatement : NSObject {
+    sqlite3_stmt *_statement;
+    NSString *_query;
+    long _useCount;
+}
+
+@property (atomic, assign) long useCount;
+@property (atomic, retain) NSString *query;
+@property (atomic, assign) sqlite3_stmt *statement;
+
+- (void)close;
+- (void)reset;
+
+@end
+

File diff suppressed because it is too large
+ 1219 - 0
FMDB2_1/FMDatabase.m


+ 37 - 0
FMDB2_1/FMDatabaseAdditions.h

@@ -0,0 +1,37 @@
+//
+//  FMDatabaseAdditions.h
+//  fmkit
+//
+//  Created by August Mueller on 10/30/05.
+//  Copyright 2005 Flying Meat Inc.. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@interface FMDatabase (FMDatabaseAdditions)
+
+
+- (int)intForQuery:(NSString*)objs, ...;
+- (long)longForQuery:(NSString*)objs, ...; 
+- (BOOL)boolForQuery:(NSString*)objs, ...;
+- (double)doubleForQuery:(NSString*)objs, ...;
+- (NSString*)stringForQuery:(NSString*)objs, ...; 
+- (NSData*)dataForQuery:(NSString*)objs, ...;
+- (NSDate*)dateForQuery:(NSString*)objs, ...;
+
+// Notice that there's no dataNoCopyForQuery:.
+// That would be a bad idea, because we close out the result set, and then what
+// happens to the data that we just didn't copy?  Who knows, not I.
+
+
+- (BOOL)tableExists:(NSString*)tableName;
+- (FMResultSet*)getSchema;
+- (FMResultSet*)getTableSchema:(NSString*)tableName;
+
+- (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName;
+
+- (BOOL)validateSQL:(NSString*)sql error:(NSError**)error;
+
+// deprecated - use columnExists:inTableWithName: instead.
+- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated));
+
+@end

+ 163 - 0
FMDB2_1/FMDatabaseAdditions.m

@@ -0,0 +1,163 @@
+//
+//  FMDatabaseAdditions.m
+//  fmkit
+//
+//  Created by August Mueller on 10/30/05.
+//  Copyright 2005 Flying Meat Inc.. All rights reserved.
+//
+
+#import "FMDatabase.h"
+#import "FMDatabaseAdditions.h"
+
+@interface FMDatabase (PrivateStuff)
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+@end
+
+@implementation FMDatabase (FMDatabaseAdditions)
+
+#define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel)             \
+va_list args;                                                        \
+va_start(args, query);                                               \
+FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args];   \
+va_end(args);                                                        \
+if (![resultSet next]) { return (type)0; }                           \
+type ret = [resultSet sel:0];                                        \
+[resultSet close];                                                   \
+[resultSet setParentDB:nil];                                         \
+return ret;
+
+
+- (NSString*)stringForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex);
+}
+
+- (int)intForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex);
+}
+
+- (long)longForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex);
+}
+
+- (BOOL)boolForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex);
+}
+
+- (double)doubleForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex);
+}
+
+- (NSData*)dataForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex);
+}
+
+- (NSDate*)dateForQuery:(NSString*)query, ... {
+    RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex);
+}
+
+
+- (BOOL)tableExists:(NSString*)tableName {
+    
+    tableName = [tableName lowercaseString];
+    
+    FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName];
+    
+    //if at least one next exists, table exists
+    BOOL returnBool = [rs next];
+    
+    //close and free object
+    [rs close];
+    
+    return returnBool;
+}
+
+/*
+ get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING]
+ check if table exist in database  (patch from OZLB)
+*/
+- (FMResultSet*)getSchema {
+    
+    //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING]
+    FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"];
+    
+    return rs;
+}
+
+/* 
+ get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER]
+*/
+- (FMResultSet*)getTableSchema:(NSString*)tableName {
+    
+    //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER]
+    FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"PRAGMA table_info('%@')", tableName]];
+    
+    return rs;
+}
+
+- (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName {
+    
+    BOOL returnBool = NO;
+    
+    tableName  = [tableName lowercaseString];
+    columnName = [columnName lowercaseString];
+    
+    FMResultSet *rs = [self getTableSchema:tableName];
+    
+    //check if column is present in table schema
+    while ([rs next]) {
+        if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString:columnName]) {
+            returnBool = YES;
+            break;
+        }
+    }
+    
+    //If this is not done FMDatabase instance stays out of pool
+    [rs close];
+    
+    return returnBool;
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+
+- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)) {
+    return [self columnExists:columnName inTableWithName:tableName];
+}
+
+#pragma clang diagnostic pop
+
+- (BOOL)validateSQL:(NSString*)sql error:(NSError**)error {
+    sqlite3_stmt *pStmt = NULL;
+    BOOL validationSucceeded = YES;
+    BOOL keepTrying = YES;
+    int numberOfRetries = 0;
+    
+    while (keepTrying == YES) {
+        keepTrying = NO;
+        int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+        if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
+            keepTrying = YES;
+            usleep(20);
+            
+            if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+                NSLog(@"Database busy");
+            }          
+        } 
+        else if (rc != SQLITE_OK) {
+            validationSucceeded = NO;
+            if (error) {
+                *error = [NSError errorWithDomain:NSCocoaErrorDomain 
+                                             code:[self lastErrorCode]
+                                         userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 
+                                                                              forKey:NSLocalizedDescriptionKey]];
+            }
+        }
+    }
+    
+    sqlite3_finalize(pStmt);
+    
+    return validationSucceeded;
+}
+
+@end

+ 75 - 0
FMDB2_1/FMDatabasePool.h

@@ -0,0 +1,75 @@
+//
+//  FMDatabasePool.h
+//  fmdb
+//
+//  Created by August Mueller on 6/22/11.
+//  Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+/*
+
+                         ***README OR SUFFER***
+Before using FMDatabasePool, please consider using FMDatabaseQueue instead.
+
+If you really really really know what you're doing and FMDatabasePool is what
+you really really need (ie, you're using a read only database), OK you can use
+it.  But just be careful not to deadlock!
+
+For an example on deadlocking, search for:
+ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD
+in the main.m file.
+
+*/
+
+
+
+@class FMDatabase;
+
+@interface FMDatabasePool : NSObject {
+    NSString            *_path;
+    
+    dispatch_queue_t    _lockQueue;
+    
+    NSMutableArray      *_databaseInPool;
+    NSMutableArray      *_databaseOutPool;
+    
+    __unsafe_unretained id _delegate;
+    
+    NSUInteger          _maximumNumberOfDatabasesToCreate;
+}
+
+@property (atomic, retain) NSString *path;
+@property (atomic, assign) id delegate;
+@property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate;
+
++ (id)databasePoolWithPath:(NSString*)aPath;
+- (id)initWithPath:(NSString*)aPath;
+
+- (NSUInteger)countOfCheckedInDatabases;
+- (NSUInteger)countOfCheckedOutDatabases;
+- (NSUInteger)countOfOpenDatabases;
+- (void)releaseAllDatabases;
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block;
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+// NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock.
+// If you need to nest, use FMDatabase's startSavePointWithName:error: instead.
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block;
+#endif
+
+@end
+
+
+@interface NSObject (FMDatabasePoolDelegate)
+
+- (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database;
+
+@end
+

+ 244 - 0
FMDB2_1/FMDatabasePool.m

@@ -0,0 +1,244 @@
+//
+//  FMDatabasePool.m
+//  fmdb
+//
+//  Created by August Mueller on 6/22/11.
+//  Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import "FMDatabasePool.h"
+#import "FMDatabase.h"
+
+@interface FMDatabasePool()
+
+- (void)pushDatabaseBackInPool:(FMDatabase*)db;
+- (FMDatabase*)db;
+
+@end
+
+
+@implementation FMDatabasePool
+@synthesize path=_path;
+@synthesize delegate=_delegate;
+@synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate;
+
+
++ (id)databasePoolWithPath:(NSString*)aPath {
+    return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
+}
+
+- (id)initWithPath:(NSString*)aPath {
+    
+    self = [super init];
+    
+    if (self != nil) {
+        _path               = [aPath copy];
+        _lockQueue          = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
+        _databaseInPool     = FMDBReturnRetained([NSMutableArray array]);
+        _databaseOutPool    = FMDBReturnRetained([NSMutableArray array]);
+    }
+    
+    return self;
+}
+
+- (void)dealloc {
+    
+    _delegate = 0x00;
+    FMDBRelease(_path);
+    FMDBRelease(_databaseInPool);
+    FMDBRelease(_databaseOutPool);
+    
+    if (_lockQueue) {
+        FMDBDispatchQueueRelease(_lockQueue);
+        _lockQueue = 0x00;
+    }
+#if ! __has_feature(objc_arc)
+    [super dealloc];
+#endif
+}
+
+
+- (void)executeLocked:(void (^)(void))aBlock {
+    dispatch_sync(_lockQueue, aBlock);
+}
+
+- (void)pushDatabaseBackInPool:(FMDatabase*)db {
+    
+    if (!db) { // db can be null if we set an upper bound on the # of databases to create.
+        return;
+    }
+    
+    [self executeLocked:^() {
+        
+        if ([_databaseInPool containsObject:db]) {
+            [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise];
+        }
+        
+        [_databaseInPool addObject:db];
+        [_databaseOutPool removeObject:db];
+        
+    }];
+}
+
+- (FMDatabase*)db {
+    
+    __block FMDatabase *db;
+    
+    [self executeLocked:^() {
+        db = [_databaseInPool lastObject];
+        
+        if (db) {
+            [_databaseOutPool addObject:db];
+            [_databaseInPool removeLastObject];
+        }
+        else {
+            
+            if (_maximumNumberOfDatabasesToCreate) {
+                NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count];
+                
+                if (currentCount >= _maximumNumberOfDatabasesToCreate) {
+                    NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount);
+                    return;
+                }
+            }
+            
+            db = [FMDatabase databaseWithPath:_path];
+        }
+        
+        //This ensures that the db is opened before returning
+        if ([db open]) {
+            if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) {
+                [db close];
+                db = 0x00;
+            }
+            else {
+                //It should not get added in the pool twice if lastObject was found
+                if (![_databaseOutPool containsObject:db]) {
+                    [_databaseOutPool addObject:db];
+                }
+            }
+        }
+        else {
+            NSLog(@"Could not open up the database at path %@", _path);
+            db = 0x00;
+        }
+    }];
+    
+    return db;
+}
+
+- (NSUInteger)countOfCheckedInDatabases {
+    
+    __block NSUInteger count;
+    
+    [self executeLocked:^() {
+        count = [_databaseInPool count];
+    }];
+    
+    return count;
+}
+
+- (NSUInteger)countOfCheckedOutDatabases {
+    
+    __block NSUInteger count;
+    
+    [self executeLocked:^() {
+        count = [_databaseOutPool count];
+    }];
+    
+    return count;
+}
+
+- (NSUInteger)countOfOpenDatabases {
+    __block NSUInteger count;
+    
+    [self executeLocked:^() {
+        count = [_databaseOutPool count] + [_databaseInPool count];
+    }];
+    
+    return count;
+}
+
+- (void)releaseAllDatabases {
+    [self executeLocked:^() {
+        [_databaseOutPool removeAllObjects];
+        [_databaseInPool removeAllObjects];
+    }];
+}
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block {
+    
+    FMDatabase *db = [self db];
+    
+    block(db);
+    
+    [self pushDatabaseBackInPool:db];
+}
+
+- (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    
+    BOOL shouldRollback = NO;
+    
+    FMDatabase *db = [self db];
+    
+    if (useDeferred) {
+        [db beginDeferredTransaction];
+    }
+    else {
+        [db beginTransaction];
+    }
+    
+    
+    block(db, &shouldRollback);
+    
+    if (shouldRollback) {
+        [db rollback];
+    }
+    else {
+        [db commit];
+    }
+    
+    [self pushDatabaseBackInPool:db];
+}
+
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    [self beginTransaction:YES withBlock:block];
+}
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    [self beginTransaction:NO withBlock:block];
+}
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    
+    static unsigned long savePointIdx = 0;
+    
+    NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
+    
+    BOOL shouldRollback = NO;
+    
+    FMDatabase *db = [self db];
+    
+    NSError *err = 0x00;
+    
+    if (![db startSavePointWithName:name error:&err]) {
+        [self pushDatabaseBackInPool:db];
+        return err;
+    }
+    
+    block(db, &shouldRollback);
+    
+    if (shouldRollback) {
+        [db rollbackToSavePointWithName:name error:&err];
+    }
+    else {
+        [db releaseSavePointWithName:name error:&err];
+    }
+    
+    [self pushDatabaseBackInPool:db];
+    
+    return err;
+}
+#endif
+
+@end

+ 38 - 0
FMDB2_1/FMDatabaseQueue.h

@@ -0,0 +1,38 @@
+//
+//  FMDatabaseQueue.h
+//  fmdb
+//
+//  Created by August Mueller on 6/22/11.
+//  Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+@class FMDatabase;
+
+@interface FMDatabaseQueue : NSObject {
+    NSString            *_path;
+    dispatch_queue_t    _queue;
+    FMDatabase          *_db;
+}
+
+@property (atomic, retain) NSString *path;
+
++ (id)databaseQueueWithPath:(NSString*)aPath;
+- (id)initWithPath:(NSString*)aPath;
+- (void)close;
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block;
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+// NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock.
+// If you need to nest, use FMDatabase's startSavePointWithName:error: instead.
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block;
+#endif
+
+@end
+

+ 176 - 0
FMDB2_1/FMDatabaseQueue.m

@@ -0,0 +1,176 @@
+//
+//  FMDatabaseQueue.m
+//  fmdb
+//
+//  Created by August Mueller on 6/22/11.
+//  Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import "FMDatabaseQueue.h"
+#import "FMDatabase.h"
+
+/*
+ 
+ Note: we call [self retain]; before using dispatch_sync, just incase 
+ FMDatabaseQueue is released on another thread and we're in the middle of doing
+ something in dispatch_sync
+ 
+ */
+ 
+@implementation FMDatabaseQueue
+
+@synthesize path = _path;
+
++ (id)databaseQueueWithPath:(NSString*)aPath {
+    
+    FMDatabaseQueue *q = [[self alloc] initWithPath:aPath];
+    
+    FMDBAutorelease(q);
+    
+    return q;
+}
+
+- (id)initWithPath:(NSString*)aPath {
+    
+    self = [super init];
+    
+    if (self != nil) {
+        
+        _db = [FMDatabase databaseWithPath:aPath];
+        FMDBRetain(_db);
+        
+        if (![_db open]) {
+            NSLog(@"Could not create database queue for path %@", aPath);
+            FMDBRelease(self);
+            return 0x00;
+        }
+        
+        _path = FMDBReturnRetained(aPath);
+        
+        _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
+    }
+    
+    return self;
+}
+
+- (void)dealloc {
+    
+    FMDBRelease(_db);
+    FMDBRelease(_path);
+    
+    if (_queue) {
+        FMDBDispatchQueueRelease(_queue);
+        _queue = 0x00;
+    }
+#if ! __has_feature(objc_arc)
+    [super dealloc];
+#endif
+}
+
+- (void)close {
+    FMDBRetain(self);
+    dispatch_sync(_queue, ^() { 
+        [_db close];
+        FMDBRelease(_db);
+        _db = 0x00;
+    });
+    FMDBRelease(self);
+}
+
+- (FMDatabase*)database {
+    if (!_db) {
+        _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]);
+        
+        if (![_db open]) {
+            NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path);
+            FMDBRelease(_db);
+            _db  = 0x00;
+            return 0x00;
+        }
+    }
+    
+    return _db;
+}
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block {
+    FMDBRetain(self);
+    
+    dispatch_sync(_queue, ^() {
+        
+        FMDatabase *db = [self database];
+        block(db);
+        
+        if ([db hasOpenResultSets]) {
+            NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
+        }
+    });
+    
+    FMDBRelease(self);
+}
+
+
+- (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    FMDBRetain(self);
+    dispatch_sync(_queue, ^() { 
+        
+        BOOL shouldRollback = NO;
+        
+        if (useDeferred) {
+            [[self database] beginDeferredTransaction];
+        }
+        else {
+            [[self database] beginTransaction];
+        }
+        
+        block([self database], &shouldRollback);
+        
+        if (shouldRollback) {
+            [[self database] rollback];
+        }
+        else {
+            [[self database] commit];
+        }
+    });
+    
+    FMDBRelease(self);
+}
+
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    [self beginTransaction:YES withBlock:block];
+}
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    [self beginTransaction:NO withBlock:block];
+}
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block {
+    
+    static unsigned long savePointIdx = 0;
+    __block NSError *err = 0x00;
+    FMDBRetain(self);
+    dispatch_sync(_queue, ^() { 
+        
+        NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
+        
+        BOOL shouldRollback = NO;
+        
+        if ([[self database] startSavePointWithName:name error:&err]) {
+            
+            block([self database], &shouldRollback);
+            
+            if (shouldRollback) {
+                [[self database] rollbackToSavePointWithName:name error:&err];
+            }
+            else {
+                [[self database] releaseSavePointWithName:name error:&err];
+            }
+            
+        }
+    });
+    FMDBRelease(self);
+    return err;
+}
+#endif
+
+@end

+ 104 - 0
FMDB2_1/FMResultSet.h

@@ -0,0 +1,104 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+#ifndef __has_feature      // Optional.
+#define __has_feature(x) 0 // Compatibility with non-clang compilers.
+#endif
+
+#ifndef NS_RETURNS_NOT_RETAINED
+#if __has_feature(attribute_ns_returns_not_retained)
+#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
+#else
+#define NS_RETURNS_NOT_RETAINED
+#endif
+#endif
+
+@class FMDatabase;
+@class FMStatement;
+
+@interface FMResultSet : NSObject {
+    FMDatabase          *_parentDB;
+    FMStatement         *_statement;
+    
+    NSString            *_query;
+    NSMutableDictionary *_columnNameToIndexMap;
+}
+
+@property (atomic, retain) NSString *query;
+@property (readonly) NSMutableDictionary *columnNameToIndexMap;
+@property (atomic, retain) FMStatement *statement;
+
++ (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;
+
+- (void)close;
+
+- (void)setParentDB:(FMDatabase *)newDb;
+
+- (BOOL)next;
+- (BOOL)hasAnotherRow;
+
+- (int)columnCount;
+
+- (int)columnIndexForName:(NSString*)columnName;
+- (NSString*)columnNameForIndex:(int)columnIdx;
+
+- (int)intForColumn:(NSString*)columnName;
+- (int)intForColumnIndex:(int)columnIdx;
+
+- (long)longForColumn:(NSString*)columnName;
+- (long)longForColumnIndex:(int)columnIdx;
+
+- (long long int)longLongIntForColumn:(NSString*)columnName;
+- (long long int)longLongIntForColumnIndex:(int)columnIdx;
+
+- (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName;
+- (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx;
+
+- (BOOL)boolForColumn:(NSString*)columnName;
+- (BOOL)boolForColumnIndex:(int)columnIdx;
+
+- (double)doubleForColumn:(NSString*)columnName;
+- (double)doubleForColumnIndex:(int)columnIdx;
+
+- (NSString*)stringForColumn:(NSString*)columnName;
+- (NSString*)stringForColumnIndex:(int)columnIdx;
+
+- (NSDate*)dateForColumn:(NSString*)columnName;
+- (NSDate*)dateForColumnIndex:(int)columnIdx;
+
+- (NSData*)dataForColumn:(NSString*)columnName;
+- (NSData*)dataForColumnIndex:(int)columnIdx;
+
+- (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx;
+- (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName;
+
+// returns one of NSNumber, NSString, NSData, or NSNull
+- (id)objectForColumnName:(NSString*)columnName;
+- (id)objectForColumnIndex:(int)columnIdx;
+
+- (id)objectForKeyedSubscript:(NSString *)columnName;
+- (id)objectAtIndexedSubscript:(int)columnIdx;
+
+/*
+If you are going to use this data after you iterate over the next row, or after you close the
+result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:)
+If you don't, you're going to be in a world of hurt when you try and use the data.
+*/
+- (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED;
+- (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED;
+
+- (BOOL)columnIndexIsNull:(int)columnIdx;
+- (BOOL)columnIsNull:(NSString*)columnName;
+
+
+/* Returns a dictionary of the row results mapped to case sensitive keys of the column names. */
+- (NSDictionary*)resultDictionary;
+ 
+/* Please use resultDictionary instead.  Also, beware that resultDictionary is case sensitive! */
+- (NSDictionary*)resultDict  __attribute__ ((deprecated));
+
+- (void)kvcMagic:(id)object;
+
+ 
+@end
+

+ 413 - 0
FMDB2_1/FMResultSet.m

@@ -0,0 +1,413 @@
+#import "FMResultSet.h"
+#import "FMDatabase.h"
+#import "unistd.h"
+
+@interface FMDatabase ()
+- (void)resultSetDidClose:(FMResultSet *)resultSet;
+@end
+
+
+@implementation FMResultSet
+@synthesize query=_query;
+@synthesize statement=_statement;
+
++ (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB {
+    
+    FMResultSet *rs = [[FMResultSet alloc] init];
+    
+    [rs setStatement:statement];
+    [rs setParentDB:aDB];
+    
+    return FMDBReturnAutoreleased(rs);
+}
+
+- (void)finalize {
+    [self close];
+    [super finalize];
+}
+
+- (void)dealloc {
+    [self close];
+    
+    FMDBRelease(_query);
+    _query = nil;
+    
+    FMDBRelease(_columnNameToIndexMap);
+    _columnNameToIndexMap = nil;
+    
+#if ! __has_feature(objc_arc)
+    [super dealloc];
+#endif
+}
+
+- (void)close {
+    [_statement reset];
+    FMDBRelease(_statement);
+    _statement = nil;
+    
+    // we don't need this anymore... (i think)
+    //[_parentDB setInUse:NO];
+    [_parentDB resultSetDidClose:self];
+    _parentDB = nil;
+}
+
+- (int)columnCount {
+    return sqlite3_column_count([_statement statement]);
+}
+
+- (NSMutableDictionary *)columnNameToIndexMap {
+    if (!_columnNameToIndexMap) {
+        int columnCount = sqlite3_column_count([_statement statement]);
+        _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount];
+        int columnIdx = 0;
+        for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+            [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
+                                      forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
+        }
+    }
+    return _columnNameToIndexMap;
+}
+
+- (void)kvcMagic:(id)object {
+    
+    int columnCount = sqlite3_column_count([_statement statement]);
+    
+    int columnIdx = 0;
+    for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+        
+        const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
+        
+        // check for a null row
+        if (c) {
+            NSString *s = [NSString stringWithUTF8String:c];
+            
+            [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]];
+        }
+    }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+
+- (NSDictionary*)resultDict {
+    
+    NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);
+    
+    if (num_cols > 0) {
+        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
+        
+        NSEnumerator *columnNames = [[self columnNameToIndexMap] keyEnumerator];
+        NSString *columnName = nil;
+        while ((columnName = [columnNames nextObject])) {
+            id objectValue = [self objectForColumnName:columnName];
+            [dict setObject:objectValue forKey:columnName];
+        }
+        
+        return FMDBReturnAutoreleased([dict copy]);
+    }
+    else {
+        NSLog(@"Warning: There seem to be no columns in this set.");
+    }
+    
+    return nil;
+}
+
+#pragma clang diagnostic pop
+
+- (NSDictionary*)resultDictionary {
+    
+    NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);
+    
+    if (num_cols > 0) {
+        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
+        
+        int columnCount = sqlite3_column_count([_statement statement]);
+        
+        int columnIdx = 0;
+        for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+            
+            NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)];
+            id objectValue = [self objectForColumnIndex:columnIdx];
+            [dict setObject:objectValue forKey:columnName];
+        }
+        
+        return dict;
+    }
+    else {
+        NSLog(@"Warning: There seem to be no columns in this set.");
+    }
+    
+    return nil;
+}
+
+
+
+
+
+- (BOOL)next {
+    
+    int rc;
+    BOOL retry;
+    int numberOfRetries = 0;
+    do {
+        retry = NO;
+        
+        rc = sqlite3_step([_statement statement]);
+        
+        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+            // this will happen if the db is locked, like if we are doing an update or insert.
+            // in that case, retry the step... and maybe wait just 10 milliseconds.
+            retry = YES;
+            if (SQLITE_LOCKED == rc) {
+                rc = sqlite3_reset([_statement statement]);
+                if (rc != SQLITE_LOCKED) {
+                    NSLog(@"Unexpected result from sqlite3_reset (%d) rs", rc);
+                }
+            }
+            usleep(20);
+            
+            if ([_parentDB busyRetryTimeout] && (numberOfRetries++ > [_parentDB busyRetryTimeout])) {
+                
+                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
+                NSLog(@"Database busy");
+                break;
+            }
+        }
+        else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
+            // all is well, let's return.
+        }
+        else if (SQLITE_ERROR == rc) {
+            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+            break;
+        } 
+        else if (SQLITE_MISUSE == rc) {
+            // uh oh.
+            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+            break;
+        }
+        else {
+            // wtf?
+            NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+            break;
+        }
+        
+    } while (retry);
+    
+    
+    if (rc != SQLITE_ROW) {
+        [self close];
+    }
+    
+    return (rc == SQLITE_ROW);
+}
+
+- (BOOL)hasAnotherRow {
+    return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW;
+}
+
+- (int)columnIndexForName:(NSString*)columnName {
+    columnName = [columnName lowercaseString];
+    
+    NSNumber *n = [[self columnNameToIndexMap] objectForKey:columnName];
+    
+    if (n) {
+        return [n intValue];
+    }
+    
+    NSLog(@"Warning: I could not find the column named '%@'.", columnName);
+    
+    return -1;
+}
+
+
+
+- (int)intForColumn:(NSString*)columnName {
+    return [self intForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (int)intForColumnIndex:(int)columnIdx {
+    return sqlite3_column_int([_statement statement], columnIdx);
+}
+
+- (long)longForColumn:(NSString*)columnName {
+    return [self longForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long)longForColumnIndex:(int)columnIdx {
+    return (long)sqlite3_column_int64([_statement statement], columnIdx);
+}
+
+- (long long int)longLongIntForColumn:(NSString*)columnName {
+    return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long long int)longLongIntForColumnIndex:(int)columnIdx {
+    return sqlite3_column_int64([_statement statement], columnIdx);
+}
+
+- (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName {
+    return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx {
+    return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx];
+}
+
+- (BOOL)boolForColumn:(NSString*)columnName {
+    return [self boolForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (BOOL)boolForColumnIndex:(int)columnIdx {
+    return ([self intForColumnIndex:columnIdx] != 0);
+}
+
+- (double)doubleForColumn:(NSString*)columnName {
+    return [self doubleForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (double)doubleForColumnIndex:(int)columnIdx {
+    return sqlite3_column_double([_statement statement], columnIdx);
+}
+
+- (NSString*)stringForColumnIndex:(int)columnIdx {
+    
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
+    
+    if (!c) {
+        // null row.
+        return nil;
+    }
+    
+    return [NSString stringWithUTF8String:c];
+}
+
+- (NSString*)stringForColumn:(NSString*)columnName {
+    return [self stringForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*)dateForColumn:(NSString*)columnName {
+    return [self dateForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*)dateForColumnIndex:(int)columnIdx {
+    
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+	return [_parentDB hasDateFormatter] ? [_parentDB dateFromString:[self stringForColumnIndex:columnIdx]] : [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]];
+}
+
+
+- (NSData*)dataForColumn:(NSString*)columnName {
+    return [self dataForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*)dataForColumnIndex:(int)columnIdx {
+    
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    int dataSize = sqlite3_column_bytes([_statement statement], columnIdx);
+    
+    NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)dataSize];
+    
+    memcpy([data mutableBytes], sqlite3_column_blob([_statement statement], columnIdx), dataSize);
+    
+    return data;
+}
+
+
+- (NSData*)dataNoCopyForColumn:(NSString*)columnName {
+    return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*)dataNoCopyForColumnIndex:(int)columnIdx {
+    
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    int dataSize = sqlite3_column_bytes([_statement statement], columnIdx);
+    
+    NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob([_statement statement], columnIdx) length:(NSUInteger)dataSize freeWhenDone:NO];
+    
+    return data;
+}
+
+
+- (BOOL)columnIndexIsNull:(int)columnIdx {
+    return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL;
+}
+
+- (BOOL)columnIsNull:(NSString*)columnName {
+    return [self columnIndexIsNull:[self columnIndexForName:columnName]];
+}
+
+- (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx {
+    
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    return sqlite3_column_text([_statement statement], columnIdx);
+}
+
+- (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName {
+    return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (id)objectForColumnIndex:(int)columnIdx {
+    int columnType = sqlite3_column_type([_statement statement], columnIdx);
+    
+    id returnValue = nil;
+    
+    if (columnType == SQLITE_INTEGER) {
+        returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]];
+    }
+    else if (columnType == SQLITE_FLOAT) {
+        returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]];
+    }
+    else if (columnType == SQLITE_BLOB) {
+        returnValue = [self dataForColumnIndex:columnIdx];
+    }
+    else {
+        //default to a string for everything else
+        returnValue = [self stringForColumnIndex:columnIdx];
+    }
+    
+    if (returnValue == nil) {
+        returnValue = [NSNull null];
+    }
+    
+    return returnValue;
+}
+
+- (id)objectForColumnName:(NSString*)columnName {
+    return [self objectForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+// returns autoreleased NSString containing the name of the column in the result set
+- (NSString*)columnNameForIndex:(int)columnIdx {
+    return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)];
+}
+
+- (void)setParentDB:(FMDatabase *)newDb {
+    _parentDB = newDb;
+}
+
+- (id)objectAtIndexedSubscript:(int)columnIdx {
+    return [self objectForColumnIndex:columnIdx];
+}
+
+- (id)objectForKeyedSubscript:(NSString *)columnName {
+    return [self objectForColumnName:columnName];
+}
+
+
+@end

+ 26 - 0
FMDB2_1/extra/FMDatabase+InMemoryOnDiskIO.h

@@ -0,0 +1,26 @@
+//
+//  FMDatabase+InMemoryOnDiskIO.h
+//  FMDB
+//
+//  Created by Peter Carr on 6/12/12.
+//
+//  I find there is a massive performance hit using an "on-disk" representation when
+//  constantly reading from or writing to the DB.  If your machine has sufficient memory, you
+//  should get a significant performance boost using an "in-memory" representation.  The FMDB
+//  warpper does not contain methods to load an "on-disk" representation into memory and
+//  similarly save an "in-memory" representation to disk.  However, SQLite3 has built-in 
+//  support for this functionality via its "Backup" API.  Here, we extend the FMBD wrapper
+//  to include this functionality.
+//
+//  http://www.sqlite.org/backup.html
+
+#import "FMDatabase.h"
+
+@interface FMDatabase (InMemoryOnDiskIO)
+
+// Loads an on-disk representation into memory.
+- (BOOL)readFromFile:(NSString*)filePath;
+
+// Saves an in-memory representation to disk
+- (BOOL)writeToFile:(NSString *)filePath;
+@end

+ 96 - 0
FMDB2_1/extra/FMDatabase+InMemoryOnDiskIO.m

@@ -0,0 +1,96 @@
+#import "FMDatabase+InMemoryOnDiskIO.h"
+
+// http://www.sqlite.org/backup.html
+static
+int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave)
+{
+    int rc;                   /* Function return code */
+    sqlite3 *pFile;           /* Database connection opened on zFilename */
+    sqlite3_backup *pBackup;  /* Backup object used to copy data */
+    sqlite3 *pTo;             /* Database to copy to (pFile or pInMemory) */
+    sqlite3 *pFrom;           /* Database to copy from (pFile or pInMemory) */
+    
+    /* Open the database file identified by zFilename. Exit early if this fails
+     ** for any reason. */
+    rc = sqlite3_open(zFilename, &pFile);
+    if( rc==SQLITE_OK ){
+        
+        /* If this is a 'load' operation (isSave==0), then data is copied
+         ** from the database file just opened to database pInMemory. 
+         ** Otherwise, if this is a 'save' operation (isSave==1), then data
+         ** is copied from pInMemory to pFile.  Set the variables pFrom and
+         ** pTo accordingly. */
+        pFrom = (isSave ? pInMemory : pFile);
+        pTo   = (isSave ? pFile     : pInMemory);
+        
+        /* Set up the backup procedure to copy from the "main" database of 
+         ** connection pFile to the main database of connection pInMemory.
+         ** If something goes wrong, pBackup will be set to NULL and an error
+         ** code and  message left in connection pTo.
+         **
+         ** If the backup object is successfully created, call backup_step()
+         ** to copy data from pFile to pInMemory. Then call backup_finish()
+         ** to release resources associated with the pBackup object.  If an
+         ** error occurred, then  an error code and message will be left in
+         ** connection pTo. If no error occurred, then the error code belonging
+         ** to pTo is set to SQLITE_OK.
+         */
+        pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
+        if( pBackup ){
+            (void)sqlite3_backup_step(pBackup, -1);
+            (void)sqlite3_backup_finish(pBackup);
+        }
+        rc = sqlite3_errcode(pTo);
+    }
+    
+    /* Close the database connection opened on database file zFilename
+     ** and return the result of this function. */
+    (void)sqlite3_close(pFile);
+    return rc;
+}
+
+
+
+@implementation FMDatabase (InMemoryOnDiskIO)
+
+- (BOOL)readFromFile:(NSString*)filePath
+{
+    // only attempt to load an on-disk representation for an in-memory database
+    if ( self->_databasePath != nil ) 
+    {
+        NSLog(@"Database is not an in-memory representation." );
+        return NO;
+    }
+    
+    // and only if the database is open
+    if ( self->_db == nil ) 
+    {
+        NSLog(@"Invalid database connection." );
+        return NO;
+    }
+    
+    return ( SQLITE_OK == loadOrSaveDb( self->_db, [filePath fileSystemRepresentation], false ) );
+
+}
+
+- (BOOL)writeToFile:(NSString *)filePath
+{
+    // only attempt to save an on-disk representation for an in-memory database
+    if ( self->_databasePath != nil )
+    {
+        NSLog(@"Database is not an in-memory representation." );
+        return NO;
+    }
+    
+    // and only if the database is open
+    if ( self->_db == nil ) 
+    {
+        NSLog(@"Invalid database connection." );
+        return NO;
+    }
+    
+    // save the in-memory representation    
+    return ( SQLITE_OK == loadOrSaveDb( self->_db, [filePath fileSystemRepresentation], true ) );
+}
+
+@end

+ 81 - 0
FSAudioController.h

@@ -0,0 +1,81 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class FSAudioStream;
+@class FSCheckContentTypeRequest;
+@class FSParsePlaylistRequest;
+@class FSParseRssPodcastFeedRequest;
+
+/**
+ * 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.
+ *
+ * Do not use this class but FSAudioStream, if you already know the content type
+ * of the URL. Using this class will generate more traffic, as the
+ * content type is checked for each URL.
+ */
+@interface FSAudioController : NSObject {
+    NSString *_url;
+    FSAudioStream *_audioStream;
+    
+    BOOL _readyToPlay;
+    
+    FSCheckContentTypeRequest *_checkContentTypeRequest;
+    FSParsePlaylistRequest *_parsePlaylistRequest;
+    FSParseRssPodcastFeedRequest *_parseRssPodcastFeedRequest;
+}
+
+/**
+ * Initializes the audio stream with an URL.
+ *
+ * @param url The URL from which the stream data is retrieved.
+ */
+- (id)initWithUrl:(NSString *)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:(NSString*)url;
+
+/**
+ * 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;
+
+/**
+ * The stream URL.
+ */
+@property (nonatomic,assign) NSString *url;
+/**
+ * The audio stream.
+ */
+@property (readonly) FSAudioStream *stream;
+
+@end

+ 272 - 0
FSAudioController.m

@@ -0,0 +1,272 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSAudioController.h"
+#import "FSAudioStream.h"
+#import "FSPlaylistItem.h"
+#import "FSCheckContentTypeRequest.h"
+#import "FSParsePlaylistRequest.h"
+#import "FSParseRssPodcastFeedRequest.h"
+
+@interface FSAudioController ()
+@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;
+@end
+
+@implementation FSAudioController
+
+@synthesize readyToPlay;
+@synthesize currentPlaylistItemIndex;
+@synthesize playlistItems;
+
+-(id)init
+{
+    if (self = [super init]) {
+        _url = nil;
+        _audioStream = [[FSAudioStream alloc] init];
+        _checkContentTypeRequest = nil;
+        _parsePlaylistRequest = nil;
+        _readyToPlay = NO;
+    }
+    return self;
+}
+
+- (id)initWithUrl:(NSString *)url
+{
+    if (self = [self init]) {
+        self.url = url;
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [_audioStream stop];
+    
+    if (_checkContentTypeRequest) {
+        [_checkContentTypeRequest cancel];
+    }
+    if (_parsePlaylistRequest) {
+        [_parsePlaylistRequest cancel];
+    }
+    if (_parseRssPodcastFeedRequest) {
+        [_parseRssPodcastFeedRequest cancel];
+    }
+}
+
+/*
+ * =======================================
+ * Properties
+ * =======================================
+ */
+
+- (FSAudioStream *)audioStream
+{
+    return _audioStream;
+}
+
+- (FSCheckContentTypeRequest *)checkContentTypeRequest
+{
+    return _checkContentTypeRequest;
+}
+
+- (FSParsePlaylistRequest *)parsePlaylistRequest
+{
+    return _parsePlaylistRequest;
+}
+
+- (FSParseRssPodcastFeedRequest *)parseRssPodcastFeedRequest
+{
+    return _parseRssPodcastFeedRequest;
+}
+
+- (BOOL)isPlaying
+{
+    return [_audioStream isPlaying];
+}
+
+/*
+ * =======================================
+ * Public interface
+ * =======================================
+ */
+
+- (void)play
+{
+    @synchronized (self) {
+        if (self.readyToPlay) {
+            if ([self.playlistItems count] > 0) {
+                FSPlaylistItem *playlistItem = (self.playlistItems)[self.currentPlaylistItemIndex];
+                
+                _audioStream.url = playlistItem.nsURL;
+            }
+            
+            [self.audioStream play];
+            return;
+        }
+        
+        __weak FSAudioController *weakSelf = self;
+        
+        /*
+         * Handle playlists
+         */
+        
+        _parsePlaylistRequest = [[FSParsePlaylistRequest alloc] init];
+        _parsePlaylistRequest.url = self.url;
+        _parsePlaylistRequest.onCompletion = ^() {
+            if ([weakSelf.parsePlaylistRequest.playlistItems count] > 0) {
+                weakSelf.playlistItems = weakSelf.parsePlaylistRequest.playlistItems;
+                
+                weakSelf.readyToPlay = YES;
+                
+                weakSelf.audioStream.onCompletion = ^() {
+                    if (weakSelf.currentPlaylistItemIndex + 1 < [weakSelf.playlistItems count]) {
+                        weakSelf.currentPlaylistItemIndex = weakSelf.currentPlaylistItemIndex + 1;
+                        
+                        [weakSelf play];
+                    }
+                };
+                
+                [weakSelf play];
+            }
+        };
+        _parsePlaylistRequest.onFailure = ^() {
+            // Failed to parse the playlist; try playing anyway
+            
+            weakSelf.readyToPlay = YES;
+            [weakSelf.audioStream play];
+        };
+        
+        /*
+         * Handle RSS feed parsing
+         */
+        
+        _parseRssPodcastFeedRequest = [[FSParseRssPodcastFeedRequest alloc] init];
+        _parseRssPodcastFeedRequest.url = self.url;
+        _parseRssPodcastFeedRequest.onCompletion = ^() {
+            if ([weakSelf.parseRssPodcastFeedRequest.playlistItems count] > 0) {
+                weakSelf.playlistItems = weakSelf.parseRssPodcastFeedRequest.playlistItems;
+                
+                weakSelf.readyToPlay = YES;
+                
+                weakSelf.audioStream.onCompletion = ^() {
+                    if (weakSelf.currentPlaylistItemIndex + 1 < [weakSelf.playlistItems count]) {
+                        weakSelf.currentPlaylistItemIndex = weakSelf.currentPlaylistItemIndex + 1;
+                        
+                        [weakSelf play];
+                    }
+                };
+                
+                [weakSelf play];
+            }
+        };
+        _parseRssPodcastFeedRequest.onFailure = ^() {
+            // Failed to parse the XML file; try playing anyway
+            
+            weakSelf.readyToPlay = YES;
+            [weakSelf.audioStream play];
+        };
+        
+        /*
+         * Handle content type check
+         */
+        
+        _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.audioStream play];
+            }
+        };
+        _checkContentTypeRequest.onFailure = ^() {
+            // Failed to check the format; try playing anyway
+            
+            weakSelf.readyToPlay = YES;
+            [weakSelf.audioStream play];
+        };
+        [_checkContentTypeRequest start];
+        
+        NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: @(kFsAudioStreamRetrievingURL)};
+        NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:nil userInfo:userInfo];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    }
+}
+
+- (void)playFromURL:(NSString*)url
+{
+    self.url = url;
+        
+    [self play];
+}
+
+- (void)stop
+{
+    [_audioStream stop];
+    self.readyToPlay = NO;
+}
+
+- (void)pause
+{
+    [_audioStream pause];
+}
+
+/*
+ * =======================================
+ * Properties
+ * =======================================
+ */
+
+- (void)setUrl:(NSString *)url
+{
+    @synchronized (self) {
+        _url = nil;
+        self.currentPlaylistItemIndex = 0;
+        
+        if (url && ![url isEqual:_url]) {
+            [_checkContentTypeRequest cancel], _checkContentTypeRequest = nil;
+            [_parsePlaylistRequest cancel], _parsePlaylistRequest = nil;
+            
+            NSString *copyOfURL = [url copy];
+            _url = copyOfURL;
+            /* Since the stream URL changed, the content may have changed */
+            self.readyToPlay = NO;
+            self.playlistItems = [[NSMutableArray alloc] init];
+        }
+    
+        self.audioStream.url = [NSURL URLWithString:_url];
+    }
+}
+
+- (NSString*)url
+{
+    if (!_url) {
+        return nil;
+    }
+    
+    NSString *copyOfURL = [_url copy];
+    return copyOfURL;
+}
+
+- (FSAudioStream *)stream
+{
+    return _audioStream;
+}
+
+@end

+ 161 - 0
FSAudioStream.h

@@ -0,0 +1,161 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * 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 enum {
+    kFsAudioStreamRetrievingURL,
+    kFsAudioStreamStopped,
+    kFsAudioStreamBuffering,
+    kFsAudioStreamPlaying,
+    kFsAudioStreamSeeking,
+    kFSAudioStreamEndOfFile,
+    kFsAudioStreamFailed
+} FSAudioStreamState;
+
+/**
+ * The audio stream errors.
+ */
+typedef enum {
+    kFsAudioStreamErrorOpen = 1,
+    kFsAudioStreamErrorStreamParse = 2,
+    kFsAudioStreamErrorNetwork = 3
+} FSAudioStreamError;
+
+@class FSAudioStreamPrivate;
+
+/**
+ * The audio stream playback position.
+ */
+typedef struct {
+    unsigned minute;
+    unsigned second;
+} FSStreamPosition;
+
+/**
+ * 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.
+ */
+@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;
+
+/**
+ * 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;
+
+/**
+ * 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;
+
+/**
+ * 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;
+
+/**
+ * Returns the playback status: YES if the stream is playing, NO otherwise.
+ */
+- (BOOL)isPlaying;
+
+/**
+ * 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;
+/**
+ * Sets a default content type for the stream. Only used when strict content
+ * type checking is disabled.
+ */
+@property (nonatomic,assign) NSString *defaultContentType;
+/**
+ * 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;
+/**
+ * The property is true if the stream is continuous (no known duration).
+ */
+@property (nonatomic,readonly) BOOL continuous;
+/**
+ * Called upon completion of the stream. Note that for continuous
+ * streams this is never called.
+ */
+@property (copy) void (^onCompletion)();
+/**
+ * Called upon a failure.
+ */
+@property (copy) void (^onFailure)();
+
+@end

+ 496 - 0
FSAudioStream.mm

@@ -0,0 +1,496 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSAudioStream.h"
+
+#import "Reachability.h"
+
+#include "audio_stream.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+#import <AudioToolbox/AudioToolbox.h>
+#endif
+
+NSString* const FSAudioStreamStateChangeNotification = @"FSAudioStreamStateChangeNotification";
+NSString* const FSAudioStreamNotificationKey_Stream = @"stream";
+NSString* const FSAudioStreamNotificationKey_State = @"state";
+
+NSString* const FSAudioStreamErrorNotification = @"FSAudioStreamErrorNotification";
+NSString* const FSAudioStreamNotificationKey_Error = @"error";
+
+NSString* const FSAudioStreamMetaDataNotification = @"FSAudioStreamMetaDataNotification";
+NSString* const FSAudioStreamNotificationKey_MetaData = @"metadata";
+
+class AudioStreamStateObserver : public astreamer::Audio_Stream_Delegate
+{
+private:
+    bool m_eofReached;
+    
+public:
+    astreamer::Audio_Stream *source;
+    FSAudioStreamPrivate *priv;
+    
+    void audioStreamErrorOccurred(int errorCode);
+    void audioStreamStateChanged(astreamer::Audio_Stream::State state);
+    void audioStreamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
+};
+
+/*
+ * ===============================================================
+ * FSAudioStream private implementation
+ * ===============================================================
+ */
+
+@interface FSAudioStreamPrivate : NSObject {
+    astreamer::Audio_Stream *_audioStream;
+    NSURL *_url;
+    BOOL _strictContentTypeChecking;
+	AudioStreamStateObserver *_observer;
+    BOOL _wasInterrupted;
+    BOOL _wasDisconnected;
+    NSString *_defaultContentType;
+    Reachability *_reachability;
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+    UIBackgroundTaskIdentifier _backgroundTask;
+#endif
+}
+
+@property (nonatomic,assign) NSURL *url;
+@property (nonatomic,assign) BOOL strictContentTypeChecking;
+@property (nonatomic,assign) NSString *defaultContentType;
+@property (nonatomic,assign) BOOL wasInterrupted;
+@property (copy) void (^onCompletion)();
+@property (copy) void (^onFailure)();
+
+- (void)reachabilityChanged:(NSNotification *)note;
+- (void)interruptionOccurred:(NSNotification *)notification;
+
+- (void)play;
+- (void)playFromURL:(NSURL*)url;
+- (void)stop;
+- (BOOL)isPlaying;
+- (void)pause;
+- (void)seekToTime:(unsigned)newSeekTime;
+- (unsigned)timePlayedInSeconds;
+- (unsigned)durationInSeconds;
+@end
+
+@implementation FSAudioStreamPrivate
+
+@synthesize wasInterrupted=_wasInterrupted;
+@synthesize onCompletion;
+@synthesize onFailure;
+
+-(id)init {
+    if (self = [super init]) {
+        _url = nil;
+        _wasInterrupted = NO;
+        _wasDisconnected = NO;
+        
+        _observer = new AudioStreamStateObserver();
+        _observer->priv = self;
+        _audioStream = new astreamer::Audio_Stream();
+        _observer->source = _audioStream;
+        _audioStream->m_delegate = _observer;
+        
+        _reachability = [Reachability reachabilityForInternetConnection];
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(reachabilityChanged:)
+                                                     name:kReachabilityChangedNotification
+                                                   object:nil];
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
+#endif
+        
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(interruptionOccurred:)
+                                                     name:AVAudioSessionInterruptionNotification
+                                                   object:nil];
+#endif
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [_reachability stopNotifier];
+    
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    
+    _audioStream->close();
+    
+    delete _audioStream, _audioStream = nil;
+    delete _observer, _observer = nil;
+}
+
+- (void)setUrl:(NSURL *)url {
+    if ([self isPlaying]) {
+        [self stop];
+    }
+    
+    @synchronized (self) {
+        if ([url isEqual:_url]) {
+            return;
+        }
+        
+        _url = [url copy];
+        
+        _audioStream->setUrl((__bridge CFURLRef)_url);
+    }
+    
+    if ([self isPlaying]) {
+        [self play];
+    }
+}
+
+- (NSURL*)url {
+    if (!_url) {
+        return nil;
+    }
+    
+    NSURL *copyOfURL = [_url copy];
+    return copyOfURL;
+}
+
+- (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking {
+    if (_strictContentTypeChecking == strictContentTypeChecking) {
+        // No change
+        return;
+    }
+    _strictContentTypeChecking = strictContentTypeChecking;
+    _audioStream->setStrictContentTypeChecking(strictContentTypeChecking);
+}
+
+- (BOOL)strictContentTypeChecking {
+    return _strictContentTypeChecking;
+}
+
+- (void)playFromURL:(NSURL*)url {
+    [self setUrl:url];
+    [self play];
+}
+
+- (void)setDefaultContentType:(NSString *)defaultContentType {
+    _defaultContentType = [defaultContentType copy];
+    std::string contentType([_defaultContentType UTF8String]);
+    _audioStream->setDefaultContentType(contentType);
+}
+
+- (NSString*)defaultContentType {
+    if (!_defaultContentType) {
+        return nil;
+    }
+    
+    NSString *copyOfDefaultContentType = [_defaultContentType copy];
+    return copyOfDefaultContentType;
+}
+
+- (void)reachabilityChanged:(NSNotification *)note {
+    Reachability *reach = [note object];
+    NetworkStatus netStatus = [reach currentReachabilityStatus];
+    BOOL internetConnectionAvailable = (netStatus == ReachableViaWiFi || netStatus == ReachableViaWWAN);
+    
+    if ([self isPlaying] && !internetConnectionAvailable) {
+        _wasDisconnected = YES;
+    }
+    
+    if (_wasDisconnected && internetConnectionAvailable) {
+        _wasDisconnected = NO;
+        
+        /*
+         * If we call play immediately after the reachability notification,
+         * the network still fails. Give some time for the network to be actually
+         * connected.
+         */
+        [NSTimer scheduledTimerWithTimeInterval:1
+                                         target:self
+                                       selector:@selector(play)
+                                       userInfo:nil
+                                        repeats:NO];
+    }
+}
+
+- (void)interruptionOccurred:(NSNotification *)notification
+{
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
+    NSNumber *interruptionType = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey];
+    if ([interruptionType intValue] == AVAudioSessionInterruptionTypeBegan) {
+        if ([self isPlaying]) {
+            self.wasInterrupted = YES;
+            
+            [self pause];
+        }
+    } else if ([interruptionType intValue] == AVAudioSessionInterruptionTypeEnded) {
+        if (self.wasInterrupted) {
+            self.wasInterrupted = NO;
+            
+            [[AVAudioSession sharedInstance] setActive:YES error:nil];
+            
+            /*
+             * Resume playing.
+             */
+            [self pause];
+        }
+    }
+#endif
+}
+
+- (void)play
+{
+    _audioStream->open();
+    
+    [_reachability startNotifier];
+}
+
+- (void)stop {
+    _audioStream->close();
+    
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+    if (_backgroundTask != UIBackgroundTaskInvalid) {
+        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
+        _backgroundTask = UIBackgroundTaskInvalid;
+    }
+#endif
+    
+    [_reachability stopNotifier];
+}
+
+- (BOOL)isPlaying {
+    return (_audioStream->state() == astreamer::Audio_Stream::PLAYING);
+}
+
+- (void)pause {
+    _audioStream->pause();
+}
+
+- (void)seekToTime:(unsigned)newSeekTime {
+    _audioStream->seekToTime(newSeekTime);
+}
+
+- (unsigned)timePlayedInSeconds {
+    return _audioStream->timePlayedInSeconds();
+}
+
+- (unsigned)durationInSeconds {
+    return _audioStream->durationInSeconds();
+}
+
+@end
+
+/*
+ * ===============================================================
+ * FSAudioStream public implementation, merely wraps the
+ * private class.
+ * ===============================================================
+ */
+
+@implementation FSAudioStream
+
+-(id)init {
+    if (self = [super init]) {
+        _private = [[FSAudioStreamPrivate alloc] init];
+    }
+    return self;
+}
+
+- (id)initWithUrl:(NSURL *)url
+{
+    if (self = [self init]) {
+        _private.url = url;
+    }
+    return self;
+}
+
+- (void)setUrl:(NSURL *)url {
+    [_private setUrl:url];
+}
+
+- (NSURL*)url {
+    return [_private url];
+}
+
+- (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking {
+    [_private setStrictContentTypeChecking:strictContentTypeChecking];
+}
+
+- (BOOL)strictContentTypeChecking {
+    return [_private strictContentTypeChecking];
+}
+
+- (void)setDefaultContentType:(NSString *)defaultContentType {
+    [_private setDefaultContentType:defaultContentType];
+}
+
+- (NSString*)defaultContentType {
+    return [_private defaultContentType];
+}
+
+- (void)play {
+    [_private play];   
+}
+
+- (void)playFromURL:(NSURL*)url {
+    [_private playFromURL:url];
+}
+
+- (void)stop {
+    [_private stop];
+}
+
+- (void)pause {
+    [_private pause];
+}
+
+- (void)seekToPosition:(FSStreamPosition)position {
+    unsigned seekTime = position.minute * 60 + position.second;
+    
+    [_private seekToTime:seekTime];
+}
+
+- (BOOL)isPlaying
+{
+    return [_private isPlaying];
+}
+
+- (FSStreamPosition)currentTimePlayed {
+    unsigned u = [_private timePlayedInSeconds];
+    
+    unsigned s,m;
+    
+    s = u % 60, u /= 60;
+    m = u;
+    
+    FSStreamPosition pos = {.minute = m, .second = s};
+    return pos;
+}
+
+- (FSStreamPosition)duration {
+    unsigned u = [_private durationInSeconds];
+    
+    unsigned s,m;
+    
+    s = u % 60, u /= 60;
+    m = u;
+    
+    FSStreamPosition pos = {.minute = m, .second = s};
+    return pos;
+}
+
+- (BOOL)continuous {
+    FSStreamPosition duration = self.duration;
+    return (duration.minute == 0 && duration.second == 0);
+}
+
+- (void (^)())onCompletion {
+    return _private.onCompletion;
+}
+
+- (void)setOnCompletion:(void (^)())onCompletion {
+    _private.onCompletion = onCompletion;
+}
+
+- (void (^)())onFailure {
+    return _private.onFailure;
+}
+
+- (void)setOnFailure:(void (^)())onFailure {
+    _private.onFailure = onFailure;
+}
+
+@end
+
+/*
+ * ===============================================================
+ * AudioStreamStateObserver: listen to the state from the audio stream.
+ * ===============================================================
+ */
+    
+void AudioStreamStateObserver::audioStreamErrorOccurred(int errorCode)
+{
+    NSDictionary *userInfo = @{FSAudioStreamNotificationKey_Error: @(errorCode),
+                              FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
+    NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamErrorNotification object:nil userInfo:userInfo];
+    
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
+}
+    
+void AudioStreamStateObserver::audioStreamStateChanged(astreamer::Audio_Stream::State state)
+{
+    NSNumber *fsAudioState;
+    
+    switch (state) {
+        case astreamer::Audio_Stream::STOPPED:
+            fsAudioState = [NSNumber numberWithInt:kFsAudioStreamStopped];
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+            [[AVAudioSession sharedInstance] setActive:NO error:nil];
+#endif
+            if (m_eofReached && priv.onCompletion) {
+                priv.onCompletion();
+            }
+            break;
+        case astreamer::Audio_Stream::BUFFERING:
+            m_eofReached = false;
+            fsAudioState = [NSNumber numberWithInt:kFsAudioStreamBuffering];
+            break;
+        case astreamer::Audio_Stream::PLAYING:
+            m_eofReached = false;
+            fsAudioState = [NSNumber numberWithInt:kFsAudioStreamPlaying];
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+            [[AVAudioSession sharedInstance] setActive:YES error:nil];
+#endif
+            break;
+        case astreamer::Audio_Stream::SEEKING:
+            m_eofReached = false;
+            fsAudioState = [NSNumber numberWithInt:kFsAudioStreamSeeking];
+            break;
+        case astreamer::Audio_Stream::END_OF_FILE:
+            m_eofReached = true;
+            fsAudioState = [NSNumber numberWithInt:kFSAudioStreamEndOfFile];
+            break;
+        case astreamer::Audio_Stream::FAILED:
+            m_eofReached = false;
+            fsAudioState = [NSNumber numberWithInt:kFsAudioStreamFailed];
+#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
+            [[AVAudioSession sharedInstance] setActive:NO error:nil];
+#endif
+            if (priv.onFailure) {
+                priv.onFailure();
+            }
+            break;
+        default:
+            /* unknown state */
+            return;
+            
+            break;
+    }
+    
+    NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: fsAudioState,
+                              FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
+    NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:nil userInfo:userInfo];
+    
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
+}
+    
+void AudioStreamStateObserver::audioStreamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
+{
+    NSMutableDictionary *metaDataDictionary = [[NSMutableDictionary alloc] init];
+    
+    for (std::map<CFStringRef,CFStringRef>::iterator iter = metaData.begin(); iter != metaData.end(); ++iter) {
+        CFStringRef key = iter->first;
+        CFStringRef value = iter->second;
+        
+        metaDataDictionary[CFBridgingRelease(key)] = CFBridgingRelease(value);
+    }
+    
+    NSDictionary *userInfo = @{FSAudioStreamNotificationKey_MetaData: metaDataDictionary,
+                              FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
+    NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamMetaDataNotification object:nil userInfo:userInfo];
+    
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
+}

+ 97 - 0
FSCheckContentTypeRequest.h

@@ -0,0 +1,97 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * Content type format.
+ */
+typedef enum {
+    kFSFileFormatUnknown = 0,
+    
+    /**
+     * Playlist.
+     */
+    kFSFileFormatM3UPlaylist,
+    kFSFileFormatPLSPlaylist,
+    
+    /**
+     * XML.
+     */
+    kFSFileFormatXML,
+    
+    /**
+     * Audio file.
+     */
+    kFSFileFormatMP3,
+    kFSFileFormatWAVE,
+    kFSFileFormatAIFC,
+    kFSFileFormatAIFF,
+    kFSFileFormatM4A,
+    kFSFileFormatMPEG4,
+    kFSFileFormatCAF,
+    kFSFileFormatAAC_ADTS,
+    
+    kFSFileFormatCount
+} FSFileFormat;
+
+/**
+ * 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<NSURLConnectionDelegate> {
+    NSString *_url;
+    NSURLConnection *_connection;
+    FSFileFormat _format;
+    NSString *_contentType;
+    BOOL _playlist;
+    BOOL _xml;
+}
+
+/**
+ * The URL of this request.
+ */
+@property (nonatomic,copy) NSString *url;
+/**
+ * Called when the content type determination is completed.
+ */
+@property (copy) void (^onCompletion)();
+/**
+ * Called if the content type determination failed.
+ */
+@property (copy) void (^onFailure)();
+/**
+ * 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

+ 174 - 0
FSCheckContentTypeRequest.m

@@ -0,0 +1,174 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSCheckContentTypeRequest.h"
+
+@implementation FSCheckContentTypeRequest
+
+@synthesize url=_url;
+@synthesize onCompletion;
+@synthesize onFailure;
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+        _format = kFSFileFormatUnknown;
+        _playlist = NO;
+        _xml = NO;
+    }
+    return self;
+}
+
+- (void)start
+{
+    if (_connection) {
+        return;
+    }
+    
+    _format = kFSFileFormatUnknown;
+    _playlist = NO;
+    _contentType = @"";
+    
+    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:_url]
+                                                           cachePolicy:NSURLRequestReloadIgnoringCacheData
+                                                       timeoutInterval:30.0];
+    [request setHTTPMethod:@"HEAD"];
+    
+    @synchronized (self) {
+        _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
+    }
+    
+    if (!_connection) {
+        onFailure();
+        return;
+    }
+}
+
+- (void)cancel
+{
+    if (!_connection) {
+        return;
+    }
+    @synchronized (self) {
+        [_connection cancel];
+        _connection = nil;
+    }
+}
+
+/*
+ * =======================================
+ * Properties
+ * =======================================
+ */
+
+- (FSFileFormat)format
+{
+    return _format;
+}
+
+- (NSString *)contentType
+{
+    return _contentType;
+}
+
+- (BOOL)playlist
+{
+    return _playlist;
+}
+
+- (BOOL)xml
+{
+    return _xml;
+}
+
+/*
+ * =======================================
+ * NSURLConnectionDelegate
+ * =======================================
+ */
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+{
+    _contentType = response.MIMEType;
+    
+    _format = kFSFileFormatUnknown;
+    _playlist = NO;
+    
+    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"]) {
+        _format = kFSFileFormatM3UPlaylist;
+        _playlist = YES;
+    } else if ([_contentType isEqualToString:@"audio/x-scpls"]) {
+        _format = kFSFileFormatPLSPlaylist;
+        _playlist = YES;
+    } else if ([_contentType isEqualToString:@"text/plain"]) {
+        /* The server did not provide meaningful content type;
+           last resort: check the file suffix, if there is one */
+        
+        NSString *absoluteUrl = [response.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 ([_contentType isEqualToString:@"text/xml"] ||
+               [_contentType isEqualToString:@"application/xml"]) {
+        _format = kFSFileFormatXML;
+        _xml = YES;
+    }
+    
+    [_connection cancel];
+    _connection = nil;
+    
+    onCompletion();
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+{
+    // Do nothing
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+    @synchronized (self) {
+        _connection = nil;
+        _format = kFSFileFormatUnknown;
+        _playlist = NO;
+    }
+    
+    onFailure();
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+{
+    // Do nothing
+}
+
+@end

+ 61 - 0
FSParsePlaylistRequest.h

@@ -0,0 +1,61 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * The playlist format.
+ */
+typedef enum {
+    kFSPlaylistFormatNone,
+    kFSPlaylistFormatM3U,
+    kFSPlaylistFormatPLS
+} FSPlaylistFormat;
+
+/**
+ * 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<NSURLConnectionDelegate> {
+    NSString *_url;
+    NSURLConnection *_connection;
+    NSInteger _httpStatus;
+    NSMutableData *_receivedData;
+    NSMutableArray *_playlistItems;
+    FSPlaylistFormat _format;
+}
+
+/**
+ * The URL of this request.
+ */
+@property (nonatomic,copy) NSString *url;
+/**
+ * Called when the playlist parsing is completed.
+ */
+@property (copy) void (^onCompletion)();
+/**
+ * Called if the playlist parsing failed.
+ */
+@property (copy) void (^onFailure)();
+/**
+ * The playlist items stored in the FSPlaylistItem class.
+ */
+@property (readonly) NSMutableArray *playlistItems;
+
+/**
+ * Starts the request.
+ */
+- (void)start;
+/**
+ * Cancels the request.
+ */
+- (void)cancel;
+
+@end

+ 259 - 0
FSParsePlaylistRequest.m

@@ -0,0 +1,259 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSParsePlaylistRequest.h"
+#import "FSPlaylistItem.h"
+
+@interface FSParsePlaylistRequest ()
+- (void)parsePlaylistFromData:(NSData *)data;
+- (void)parsePlaylistM3U:(NSString *)playlist;
+- (void)parsePlaylistPLS:(NSString *)playlist;
+
+@property (readonly) FSPlaylistFormat format;
+
+@end
+
+@implementation FSParsePlaylistRequest
+
+@synthesize url=_url;
+@synthesize onCompletion;
+@synthesize onFailure;
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+    }
+    return self;
+}
+
+- (void)start
+{
+    if (_connection) {
+        return;
+    }
+    
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.url]
+                                             cachePolicy:NSURLRequestUseProtocolCachePolicy
+                                         timeoutInterval:60.0];
+    
+    @synchronized (self) {
+        _receivedData = [NSMutableData data];
+        _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
+        _playlistItems = [[NSMutableArray alloc] init];
+        _format = kFSPlaylistFormatNone;
+    }
+    
+    if (!_connection) {
+        onFailure();
+        return;
+    }
+}
+
+- (void)cancel
+{
+    if (!_connection) {
+        return;
+    }
+    @synchronized (self) {
+        [_connection cancel];
+        _connection = 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];
+    } else if (_format == kFSPlaylistFormatPLS) {
+        [self parsePlaylistPLS:playlistData];
+    }
+}
+
+- (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 = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+            
+            [_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 = file;
+            
+            [_playlistItems addObject:item];
+        }
+    }
+}
+
+/*
+ * =======================================
+ * NSURLConnectionDelegate
+ * =======================================
+ */
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+{
+    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+    _httpStatus = [httpResponse statusCode];
+    
+    NSString *contentType = response.MIMEType;
+    NSString *absoluteUrl = [response.URL absoluteString];
+    
+    _format = kFSPlaylistFormatNone;
+    
+    if ([contentType isEqualToString:@"audio/x-mpegurl"]) {
+        _format = kFSPlaylistFormatM3U;
+    } else if ([contentType isEqualToString:@"audio/x-scpls"]) {
+        _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) {
+        [_connection cancel];
+        onFailure();
+    }
+
+    [_receivedData setLength:0];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+{
+    [_receivedData appendData:data];
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+    @synchronized (self) {
+        _connection = nil;
+        _receivedData = nil;
+    }
+    
+    onFailure();
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+{
+    assert(_connection == connection);
+    
+    @synchronized (self) {
+        _connection = nil;
+    }
+    
+    if (_httpStatus != 200) {
+        onFailure();
+        return;
+    }
+    
+    [self parsePlaylistFromData:_receivedData];
+    
+    onCompletion();
+}
+
+@end

+ 26 - 0
FSParseRssPodcastFeedRequest.h

@@ -0,0 +1,26 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#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

+ 61 - 0
FSParseRssPodcastFeedRequest.m

@@ -0,0 +1,61 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSParseRssPodcastFeedRequest.h"
+#import "FSPlaylistItem.h"
+
+static NSString *const kXPathQueryItems = @"/rss/channel/item";
+
+@interface FSParseRssPodcastFeedRequest (PrivateMethods)
+- (void)parseItems:(xmlNodePtr)node;
+@end
+
+@implementation FSParseRssPodcastFeedRequest
+
+- (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"]) {
+            item.url = [self contentForNodeAttribute:n attribute:"url"];
+        }
+    }
+    
+    [_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

+ 30 - 0
FSPlaylistItem.h

@@ -0,0 +1,30 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * A playlist item. Each item has a title and url.
+ */
+@interface FSPlaylistItem : NSObject {
+    NSString *_title;
+    NSString *_url;
+}
+
+/**
+ * The title of the playlist.
+ */
+@property (nonatomic,copy) NSString *title;
+/**
+ * The URL of the playlist.
+ */
+@property (nonatomic,copy) NSString *url;
+/**
+ * The NSURL of the playlist.
+ */
+@property (weak, readonly) NSURL *nsURL;
+
+@end

+ 39 - 0
FSPlaylistItem.m

@@ -0,0 +1,39 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSPlaylistItem.h"
+
+@implementation FSPlaylistItem
+
+@synthesize title=_title;
+@synthesize url=_url;
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        // Initialization code here.
+    }
+    
+    return self;
+}
+
+- (NSURL *)nsURL {
+    return [NSURL URLWithString:_url];
+}
+
+- (BOOL)isEqual:(id)anObject
+{
+    FSPlaylistItem *otherObject = anObject;
+    
+    if ([otherObject.title isEqual:self.title] &&
+        [otherObject.url isEqual:self.url]) {
+        return YES;
+    }
+    
+    return NO;
+}
+
+@end

+ 98 - 0
FSXMLHttpRequest.h

@@ -0,0 +1,98 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import <Foundation/Foundation.h>
+
+#include <libxml/parser.h>
+
+/**
+ * XML HTTP request error status.
+ */
+typedef enum {
+    FSXMLHttpRequestError_NoError = 0,
+    FSXMLHttpRequestError_Connection_Failed,
+    FSXMLHttpRequestError_Invalid_Http_Status,
+    FSXMLHttpRequestError_XML_Parser_Failed
+} FSXMLHttpRequestError;
+
+/**
+ * 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<NSURLConnectionDelegate> {
+    NSString *_url;
+    NSURLConnection *_connection;
+    NSInteger _httpStatus;
+    NSMutableData *_receivedData;
+    xmlDocPtr _xmlDocument;
+    FSXMLHttpRequestError lastError;
+    NSDateFormatter *_dateFormatter;
+}
+
+/**
+ * The URL of the request.
+ */
+@property (nonatomic,copy) NSString *url;
+/**
+ * Called upon completion of the request.
+ */
+@property (copy) void (^onCompletion)();
+/**
+ * Called upon a failure.
+ */
+@property (copy) void (^onFailure)();
+/**
+ * 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

+ 269 - 0
FSXMLHttpRequest.m

@@ -0,0 +1,269 @@
+/*
+ * This file is part of the FreeStreamer project,
+ * (C)Copyright 2011-2013 Matias Muhonen.
+ * See the file ''LICENSE'' for using the code.
+ */
+
+#import "FSXMLHttpRequest.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
+
+@synthesize url=_url;
+@synthesize onCompletion;
+@synthesize onFailure;
+@synthesize lastError=_lastError;
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+        _dateFormatter = [[NSDateFormatter alloc] init];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    _receivedData = nil;
+}
+
+- (void)start
+{
+    if (_connection) {
+        return;
+    }
+    
+    _lastError = FSXMLHttpRequestError_NoError;
+    
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.url]
+                                             cachePolicy:NSURLRequestUseProtocolCachePolicy
+                                         timeoutInterval:60.0];
+    
+    @synchronized (self) {
+        _receivedData = [NSMutableData data];
+        _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
+    }
+    
+    if (!_connection) {
+        onFailure();
+        return;
+    }
+}
+
+- (void)cancel
+{
+    if (!_connection) {
+        return;
+    }
+    @synchronized (self) {
+        [_connection cancel];
+        _connection = nil;
+    }
+}
+
+/*
+ * =======================================
+ * NSURLConnectionDelegate
+ * =======================================
+ */
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+{
+    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+    _httpStatus = [httpResponse statusCode];
+    
+    [_receivedData setLength:0];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+{
+    [_receivedData appendData:data];
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+    @synchronized (self) {
+        assert(_connection == connection);
+        _connection = nil;
+        _receivedData = nil;
+    }
+    
+    _lastError = FSXMLHttpRequestError_Connection_Failed;
+    onFailure();
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+{
+    assert(_connection == connection);
+    
+    @synchronized (self) {
+        _connection = nil;
+    }
+    
+    if (_httpStatus != 200) {
+        _lastError = FSXMLHttpRequestError_Invalid_Http_Status;
+        onFailure();
+        return;
+    }
+    
+    const char *encoding = [self detectEncoding];
+    
+    _xmlDocument = xmlReadMemory([_receivedData bytes],
+                                 (int)[_receivedData length],
+                                 "",
+                                 encoding,
+                                 0);
+    
+    if (!_xmlDocument) {
+        _lastError = FSXMLHttpRequestError_XML_Parser_Failed;
+        onFailure();
+        return;
+    }
+    
+    [self parseResponseData];
+    
+    xmlFreeDoc(_xmlDocument), _xmlDocument = nil;
+    
+    onCompletion();
+}
+
+/*
+ * =======================================
+ * 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
+{
+    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

File diff suppressed because it is too large
+ 1321 - 0
FreeMusic.xcodeproj/project.pbxproj


+ 7 - 0
FreeMusic.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:FreeMusic.xcodeproj">
+   </FileRef>
+</Workspace>

二進制
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/gulizan.xcuserdatad/UserInterfaceState.xcuserstate


二進制
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/jiamingyan.xcuserdatad/UserInterfaceState.xcuserstate


二進制
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/julie.xcuserdatad/UserInterfaceState.xcuserstate


+ 10 - 0
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/julie.xcuserdatad/WorkspaceSettings.xcsettings

@@ -0,0 +1,10 @@
+<?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>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
+	<true/>
+	<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
+	<true/>
+</dict>
+</plist>

二進制
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/yanjiaming.xcuserdatad/UserInterfaceState.xcuserstate


二進制
FreeMusic.xcodeproj/project.xcworkspace/xcuserdata/zhaojianguo.xcuserdatad/UserInterfaceState.xcuserstate


+ 101 - 0
FreeMusic.xcodeproj/xcuserdata/gulizan.xcuserdatad/xcschemes/FreeMusic.xcscheme

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0820"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+               BuildableName = "FreeMusic.app"
+               BlueprintName = "FreeMusic"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </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">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 0
FreeMusic.xcodeproj/xcuserdata/gulizan.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+	<dict>
+		<key>FreeMusic.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>AAB0DBF7193416CF00DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>AAB0DC12193416D000DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 101 - 0
FreeMusic.xcodeproj/xcuserdata/jiamingyan.xcuserdatad/xcschemes/FreeMusic.xcscheme

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0830"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+               BuildableName = "FreeMusic.app"
+               BlueprintName = "FreeMusic"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </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">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 0
FreeMusic.xcodeproj/xcuserdata/jiamingyan.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+	<dict>
+		<key>FreeMusic.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>AAB0DBF7193416CF00DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>AAB0DC12193416D000DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 5 - 0
FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   type = "1"
+   version = "2.0">
+</Bucket>

+ 112 - 0
FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcschemes/FreeMusic.xcscheme

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0620"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+               BuildableName = "FreeMusic.app"
+               BlueprintName = "FreeMusic"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "NO"
+            buildForArchiving = "NO"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      buildConfiguration = "Debug">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </TestAction>
+   <LaunchAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Debug"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Release"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 0
FreeMusic.xcodeproj/xcuserdata/julie.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+	<dict>
+		<key>FreeMusic.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>AAB0DBF7193416CF00DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>AAB0DC12193416D000DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 101 - 0
FreeMusic.xcodeproj/xcuserdata/yanjiaming.xcuserdatad/xcschemes/FreeMusic.xcscheme

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0730"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+               BuildableName = "FreeMusic.app"
+               BlueprintName = "FreeMusic"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </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">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 0
FreeMusic.xcodeproj/xcuserdata/yanjiaming.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+	<dict>
+		<key>FreeMusic.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>AAB0DBF7193416CF00DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>AAB0DC12193416D000DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 5 - 0
FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   type = "1"
+   version = "2.0">
+</Bucket>

+ 96 - 0
FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcschemes/FreeMusic.xcscheme

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0510"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+               BuildableName = "FreeMusic.app"
+               BlueprintName = "FreeMusic"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      buildConfiguration = "Debug">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AAB0DC12193416D000DE28CD"
+               BuildableName = "FreeMusicTests.xctest"
+               BlueprintName = "FreeMusicTests"
+               ReferencedContainer = "container:FreeMusic.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </TestAction>
+   <LaunchAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Debug"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Release"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AAB0DBF7193416CF00DE28CD"
+            BuildableName = "FreeMusic.app"
+            BlueprintName = "FreeMusic"
+            ReferencedContainer = "container:FreeMusic.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 0
FreeMusic.xcodeproj/xcuserdata/zhaojianguo.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+	<dict>
+		<key>FreeMusic.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>AAB0DBF7193416CF00DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>AAB0DC12193416D000DE28CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 28 - 0
FreeMusic/AppDelegate.h

@@ -0,0 +1,28 @@
+//
+//  AppDelegate.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+@class FMMainViewController,FMHomeViewController,DownViewController,QingXuViewController;
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+{
+    NSInteger index;
+    NSMutableArray * array;
+}
+@property (strong, nonatomic) UIWindow *window;
+@property (strong, nonatomic) FMMainViewController *mainViewController;
+@property (strong, nonatomic) UINavigationController * rootNavigationController;
+@property (strong, nonatomic) FMHomeViewController * homeViewController;
+@property (strong, nonatomic) UINavigationController * homeNavigationController;
+
+@property (strong, nonatomic) QingXuViewController * qingxuViewController;
+@property (strong, nonatomic) UINavigationController * qingxuNavigationController;
+@property (strong, nonatomic) DownViewController * tudouViewController;
+@property (strong, nonatomic) UINavigationController * tudouNavigationController;
+
+
+@property (strong, nonatomic) UITabBarController * tabBarController;
+
+@end

+ 134 - 0
FreeMusic/AppDelegate.m

@@ -0,0 +1,134 @@
+//
+//  AppDelegate.m
+//  FreeMusic
+//
+//
+
+#import "AppDelegate.h"
+#import "MCDataEngine.h"
+#import "Globle.h"
+#import "FMSingerModel.h"
+#import "FMMainViewController.h"
+#import "FMHomeViewController.h"
+#import "DBDaoHelper.h"
+#import "DownViewController.h"
+#import "QingXuViewController.h"
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+    // Override point for customization after application launch.
+    self.window.backgroundColor = [UIColor whiteColor];
+    Globle * globle = [Globle shareGloble];
+    [globle copySqlitePath];
+    globle.isPlaying = YES;
+    [DBDaoHelper createAllTable];
+    
+    self.mainViewController = [[FMMainViewController alloc] init];
+    self.rootNavigationController = [[UINavigationController alloc] initWithRootViewController:self.mainViewController];
+    
+    self.homeViewController = [[FMHomeViewController alloc] init];
+    self.homeNavigationController = [[UINavigationController alloc] initWithRootViewController:self.homeViewController];
+    
+    self.qingxuViewController = [[QingXuViewController alloc] init];
+    self.qingxuNavigationController = [[UINavigationController alloc] initWithRootViewController:self.qingxuViewController];
+    
+    self.tudouViewController = [[DownViewController alloc] init];
+    self.tudouNavigationController = [[UINavigationController alloc] initWithRootViewController:self.tudouViewController];
+
+
+    self.tabBarController = [[UITabBarController alloc] init];
+    self.tabBarController.viewControllers = [NSArray arrayWithObjects:self.homeNavigationController,self.qingxuNavigationController,self.rootNavigationController,self.tudouNavigationController, nil];
+    
+    self.homeNavigationController.tabBarItem.title = @"我的分类";
+    self.homeNavigationController.tabBarItem.image = [UIImage imageNamed:@"mymusci"];
+    
+    self.qingxuNavigationController.tabBarItem.title = @"我的心情";
+    self.qingxuNavigationController.tabBarItem.image = [UIImage imageNamed:@"tab_grzx_pres"];
+    
+    self.rootNavigationController.tabBarItem.title = @"明星列表";
+    self.rootNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabbarSearch"];
+    self.tudouNavigationController.tabBarItem.title = @"本地下载";
+    self.tudouNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabbarMovie"];
+
+    self.tabBarController.selectedViewController = self.rootNavigationController;
+    self.window.rootViewController = self.tabBarController;
+    [self.window makeKeyAndVisible];
+    return YES;
+}
+
+ -(void)addSingerToDB
+{
+    long long uid = 60467713;//[[array objectAtIndex:index] longLongValue];
+    MCDataEngine * engine = [MCDataEngine new];
+    [engine getSingerInformationWith:uid WithCompletionHandler:^(NSArray *array) {
+    } errorHandler:^(NSError *error) {
+        
+    }];
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application
+{
+    //NSLog(@"程序已经进入后台...");
+    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
+    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
+	[self becomeFirstResponder];
+    [Globle shareGloble].isApplicationEnterBackground = YES;
+    [[NSNotificationCenter defaultCenter] postNotificationName:FMRFRadioViewSetSongInformationNotification object:nil userInfo:nil];
+}
+
+//耳机线控代码
+/*- (void)remoteControlReceivedWithEvent:(UIEvent *)event
+{
+    NSString * statu = nil;
+	if (event.type == UIEventTypeRemoteControl) {
+        switch (event.subtype) {
+            case UIEventSubtypeRemoteControlPause:
+                statu = @"UIEventSubtypeRemoteControlPause";
+                break;
+            case UIEventSubtypeRemoteControlPreviousTrack:
+                statu = @"UIEventSubtypeRemoteControlPreviousTrack";
+                break;
+            case UIEventSubtypeRemoteControlNextTrack:
+                statu = @"UIEventSubtypeRemoteControlNextTrack";
+                break;
+            case UIEventSubtypeRemoteControlPlay:
+                statu = @"UIEventSubtypeRemoteControlPlay";
+                break;
+            default:
+                break;
+        }
+    }
+    NSMutableDictionary * dict = [NSMutableDictionary new];
+    [dict setObject:statu forKey:@"keyStatus"];
+    [[NSNotificationCenter defaultCenter] postNotificationName:FMRFRadioViewStatusNotifiation object:nil userInfo:dict];
+}*/
+
+
+- (void)applicationWillEnterForeground:(UIApplication *)application
+{
+    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+     //NSLog(@"程序已经获得焦点...");
+    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+    [Globle shareGloble].isApplicationEnterBackground = NO;
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+    //NSLog(@"程序将要终止");
+    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end

+ 16 - 0
FreeMusic/DownViewController.h

@@ -0,0 +1,16 @@
+//
+//  DownViewController.h
+//  FreeMusic
+//
+//
+
+#import "FMBaseViewController.h"
+
+@interface DownViewController : FMBaseViewController<UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate>
+{
+    UITableView * _tableView;
+    
+}
+
+
+@end

+ 104 - 0
FreeMusic/DownViewController.m

@@ -0,0 +1,104 @@
+//
+//  DownViewController.m
+//  FreeMusic
+//
+//
+
+#import "DownViewController.h"
+#import "DBDaoHelper.h"
+#import "FMMusicViewController.h"
+#import "MusicModel.h"
+@interface DownViewController ()
+{
+    NSArray * array;
+}
+@end
+
+@implementation DownViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+    self.navigation.title = @"下载列表";
+    [self.navigationController setNavigationBarHidden:YES animated:NO];
+    self.view.backgroundColor = [UIColor whiteColor];
+    self.navigation.leftImage  =nil;// [UIImage imageNamed:@"nav_backbtn.png"];
+    self.navigation.rightImage = nil;
+    self.navigation.navigaionBackColor = [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.navigation.size.height+self.navigation.origin.y, self.view.size.width, self.view.size.height-self.navigation.size.height-48.5f)];
+    _tableView.delegate =self;
+    _tableView.dataSource = self;
+    [self.view addSubview:_tableView];
+}
+
+
+-(void)viewWillAppear:(BOOL)animated{
+    array = [DBDaoHelper selectDownMusicWithType:1];
+    [_tableView reloadData];
+}
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    //#warning Potentially incomplete method implementation.
+    // Return the number of sections.
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    //#warning Incomplete method implementation.
+    // Return the number of rows in the section.
+    return [array count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSString * dentifier = @"cell";
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:dentifier];
+    if (cell == nil) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:dentifier];
+    }
+    FMSongListModel * model = [array objectAtIndex:indexPath.row];
+    cell.textLabel.text = model.title;
+    return cell;
+}
+
+-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return 44;
+}
+-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+//    NSMutableArray *arr = [[NSMutableArray alloc]init];
+//    for (NSInteger i = 0; i<array.count; i++) {
+//        FMSongListModel * model1 = [[FMSongListModel alloc]init];
+//         MusicModel * musicModel1 = [array objectAtIndex:indexPath.row];
+//        model1.song_id =musicModel1.songid;
+//        [arr addObject:model1];
+//    }
+     FMSongListModel * model = [array objectAtIndex:indexPath.row];
+    
+    FMMusicViewController * pushController = [FMMusicViewController shareMusicViewController];
+    pushController.songListModel = model;
+    pushController.array = array;
+    pushController.index = indexPath.row;
+    
+    pushController.only = NO;
+    [pushController playMusicWithSongLink:model];
+    [self.navigationController pushViewController:pushController animated:YES];
+}
+
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 23 - 0
FreeMusic/FMBaseViewController.h

@@ -0,0 +1,23 @@
+//
+//  BaseViewController.h
+//  Canada
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "ZZNavigationView.h"
+#import "UIView+Additions.h"
+#import "FMSongListModel.h"
+#import "FMSingerModel.h"
+#import "UIImageView+WebCache.h"
+#import "MCDataEngine.h"
+#import "Globle.h"
+#import "ProgressHUD.h"
+
+
+@interface FMBaseViewController : UIViewController<ZZNavigationViewDelegate>
+{
+    Globle * globle;
+}
+@property (nonatomic,strong) ZZNavigationView * navigation;
+@end

+ 72 - 0
FreeMusic/FMBaseViewController.m

@@ -0,0 +1,72 @@
+//
+//  BaseViewController.m
+//  Canada
+//
+//
+
+#import "FMBaseViewController.h"
+@interface FMBaseViewController ()
+
+@end
+
+@implementation FMBaseViewController
+@synthesize navigation = _navigation;
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        // Custom initialization
+    }
+    return self;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    
+    self.view.backgroundColor = [UIColor whiteColor];
+    
+    NSString * systeam = [UIDevice currentDevice].systemVersion;
+    float number = [systeam floatValue];
+    
+    CGFloat height = 0.0f;
+    NSInteger type = 0;
+    if (number <= 6.9) {
+        type = 0;
+        height = 44.0f;
+    }else{
+        type = 1;
+        height = 66.0f;
+    }
+    
+    globle = [Globle shareGloble];
+    
+    self.navigation = [[ZZNavigationView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, height)];
+    _navigation.type = type;
+    _navigation.leftImage  = [UIImage imageNamed:@"nav_backbtn.png"];
+    _navigation.delegate = self;
+    _navigation.backgroundColor = [UIColor whiteColor];
+    [self.view addSubview:_navigation];
+
+	// Do any additional setup after loading the view.
+}
+
+#pragma mark ##### ZZNavigationViewDelegate ####
+-(void)previousToViewController
+{
+    [self.navigationController popViewControllerAnimated:YES];
+}
+
+-(void)rightButtonClickEvent
+{
+    
+}
+
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+    // Dispose of any resources that can be recreated.
+}
+
+@end

+ 16 - 0
FreeMusic/FMHomeViewController.h

@@ -0,0 +1,16 @@
+//
+//  FMHomeViewController.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "FMBaseViewController.h"
+@interface FMHomeViewController : FMBaseViewController<UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate>
+{
+    UITableView * _tableView;
+    
+}
+@property (strong,nonatomic) UISearchBar *searchBar;
+
+@end

+ 180 - 0
FreeMusic/FMHomeViewController.m

@@ -0,0 +1,180 @@
+//
+//  FMHomeViewController.m
+//  FreeMusic
+//
+//
+
+#import "FMHomeViewController.h"
+#import "FMMusicViewController.h"
+#import "ShouCangVC.h"
+#import "SearchViewController.h"
+#import "DBDaoHelper.h"
+@interface FMHomeViewController ()
+{
+    NSArray * array;
+}
+@end
+@implementation FMHomeViewController
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+//    self.view.backgroundColor = [UIColor redColor];
+    self.navigation.title = @"分类";
+    [self.navigationController setNavigationBarHidden:YES animated:NO];
+    self.view.backgroundColor = [UIColor whiteColor];
+    self.navigation.leftImage  =nil;// [UIImage imageNamed:@"nav_backbtn.png"];
+    self.navigation.rightImage = nil;
+    //    self.navigation.headerImage = [UIImage imageNamed:@"nav_canadaicon.png"];
+    self.navigation.navigaionBackColor = [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+    self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0f,self.navigation.size.height+self.navigation.origin.y, self.view.size.width, 44.0f)];
+    _searchBar.delegate =self;
+    _searchBar.placeholder = @"搜索:音乐";
+    //	_searchBar.tintColor = [UIColor lightGrayColor];
+    _searchBar.showsCancelButton = YES;
+    _searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
+    _searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
+    _searchBar.keyboardType = UIKeyboardTypeDefault;
+    
+    
+  	_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _searchBar.size.height+_searchBar.origin.y, self.view.size.width, self.view.size.height-self.navigation.size.height-self.searchBar.size.height-48.5f)];
+    _tableView.delegate =self;
+    _tableView.dataSource = self;
+//    _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+    [self.view addSubview:_tableView];
+    
+//    array = [NSArray arrayWithObjects:@"欢快",@"甜蜜" ,@"安静",@"伤感",@"思念",nil];
+    array = [DBDaoHelper myType];
+    [self.view addSubview:_searchBar];
+    
+    UIButton * _buttonFriend= [UIButton buttonWithType:UIButtonTypeCustom];
+    [_buttonFriend setTitle:@"添加分类" forState:UIControlStateNormal];
+    [_buttonFriend setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
+    //    [_buttonFriend setTitleColor:kColor(220, 151, 32, 1) forState:UIControlStateNormal];
+    [_buttonFriend addTarget:self action:@selector(topViewClick) forControlEvents:UIControlEventTouchUpInside];
+    _buttonFriend.titleLabel.font = [UIFont systemFontOfSize:15];
+    _buttonFriend.frame = CGRectMake(230-30, 30, 75+30, 30);
+    //    [_buttonFriend setBackgroundColor:[UIColor greenColor]];
+    [self.navigation addSubview:_buttonFriend];
+}
+-(void)topViewClick{
+    UIAlertView *customAlertView = [[UIAlertView alloc] initWithTitle:@"请输入您的分类名称" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
+    
+    [customAlertView setAlertViewStyle:UIAlertViewStylePlainTextInput];
+    
+    UITextField *nameField = [customAlertView textFieldAtIndex:0];
+    nameField.placeholder = @"添加分类";
+
+    [customAlertView show];
+    
+}
+//弹出的代理
+-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
+    
+    if (buttonIndex == alertView.firstOtherButtonIndex) {
+        UITextField *nameField = [alertView textFieldAtIndex:0];
+        
+        [DBDaoHelper insertMyTypeWith:nameField.text];
+         array = [DBDaoHelper myType];
+        [_tableView reloadData];
+        //TODO
+    }
+    
+    
+    
+}
+//搜索取消按钮点击
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+    [_searchBar resignFirstResponder];
+}
+//搜索按钮点击,进入搜索我的歌曲页面
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+    // called when keyboard search button pressed
+    [_searchBar resignFirstResponder];
+    
+    SearchViewController * pushController = [[SearchViewController alloc] init];
+    pushController.nameMusic = searchBar.text;
+    [self.navigationController pushViewController:pushController animated:YES];
+}
+#pragma mark - Table view data source,下面都是列表的代理事件
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+
+    return [array count];
+}
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSString * dentifier = @"cell";
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:dentifier];
+    if (cell == nil) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:dentifier];
+    }
+    cell.textLabel.text = [array objectAtIndex:indexPath.row];
+    return cell;
+}
+
+-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return 44;
+}
+//列表点击代理
+-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+
+    ShouCangVC * pushController = [[ShouCangVC alloc] init];
+    pushController.typeMusic = indexPath.row+1;
+     [self.navigationController pushViewController:pushController animated:YES];
+}
+//设置左滑出现删除按钮
+- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
+
+           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath      //当在Cell上滑动时会调用此函数
+
+{
+    
+    if(indexPath.row>=5)
+        
+        return  UITableViewCellEditingStyleDelete;  //返回此值时,Cell会做出响应显示Delete按键,点击Delete后会调用下面的函数,别给传递UITableViewCellEditingStyleDelete参数
+    
+    else
+        
+        return  UITableViewCellEditingStyleNone;//返回此值时,Cell上不会出现Delete按键,即Cell不做任何响应
+    
+}
+
+
+//删除按钮点击
+- (void) tableView:(UITableView *)tableView
+
+commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
+
+ forRowAtIndexPath:(NSIndexPath *)indexPath//对选中的Cell根据editingStyle进行操作
+
+{
+    
+    if (editingStyle == UITableViewCellEditingStyleDelete)
+        
+    {
+        NSArray *arrTypeId = [DBDaoHelper myTypeId];
+        NSString *type = [arrTypeId objectAtIndex:indexPath.row];
+        
+        [DBDaoHelper DeleteMyTypeWith:type];
+        array = [DBDaoHelper myType];
+        [_tableView reloadData];
+        
+    }
+    
+}
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+}
+@end

+ 12 - 0
FreeMusic/FMLoadMoreFooterView.h

@@ -0,0 +1,12 @@
+//
+//  FMLoadMoreFooterView.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FMLoadMoreFooterView : UIView
+@property (nonatomic,strong) UIActivityIndicatorView * activeView;
+@property (nonatomic,strong) UILabel * titleLabel;
+@end

+ 37 - 0
FreeMusic/FMLoadMoreFooterView.m

@@ -0,0 +1,37 @@
+//
+//  FMLoadMoreFooterView.m
+//  FreeMusic
+//
+//
+
+#import "FMLoadMoreFooterView.h"
+#import "UIView+Additions.h"
+@implementation FMLoadMoreFooterView
+
+- (id)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        // Initialization code
+        self.activeView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(frame.size.width/2-(30+100)/2, frame.size.height/2-30/2, 30, 30)];
+        _activeView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
+//        _activeView.backgroundColor =[UIColor redColor];
+        [self addSubview:_activeView];
+        
+        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(_activeView.size.width+_activeView.origin.x, frame.size.height/2-25/2, 100, 25)];
+//        _titleLabel.backgroundColor = [UIColor redColor];
+        [self addSubview:_titleLabel];
+    }
+    return self;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect
+{
+    // Drawing code
+}
+*/
+
+@end

+ 20 - 0
FreeMusic/FMLrcView.h

@@ -0,0 +1,20 @@
+//
+//  FMLrcView.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FMLrcView : UIView
+{
+    UIScrollView * _scrollView;
+    NSMutableArray * keyArr;
+    NSMutableArray * titleArr;
+    NSMutableArray * labels;
+}
+-(void)setLrcSourcePath:(NSString *)path;
+-(void)scrollViewMoveLabelWith:(NSString *)string;
+-(void)scrollViewClearSubView;
+-(void)selfClearKeyAndTitle;
+@end

+ 161 - 0
FreeMusic/FMLrcView.m

@@ -0,0 +1,161 @@
+//
+//  FMLrcView.m
+//  FreeMusic
+//
+//
+
+#import "FMLrcView.h"
+#import "UIView+Additions.h"
+@implementation FMLrcView
+
+- (id)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        keyArr = [NSMutableArray new];
+        titleArr = [NSMutableArray new];
+        labels = [NSMutableArray new];
+        
+        _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
+        _scrollView.userInteractionEnabled = NO;
+        [self addSubview:_scrollView];        
+    }
+    return self;
+}
+-(void)setLrcSourcePath:(NSString *)path
+{
+    if ([keyArr count]!=0) {
+        [keyArr removeAllObjects];
+    }
+    if ([titleArr count]!=0) {
+        [titleArr removeAllObjects];
+    }
+    NSString * string =[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
+    NSArray *array = [string componentsSeparatedByString:@"\n"];
+    for (NSString * str in array) {
+        if (!str || str.length <= 0)
+            continue;
+        [self parseLrcLine:str];
+    }
+    [self bubbleSort:keyArr];
+    
+    _scrollView.contentOffset = CGPointMake(0, 0);
+    _scrollView.contentSize = CGSizeMake(_scrollView.size.width, [keyArr count]*25);
+    [self addLabelForScrollView];
+}
+-(float)getNumberWith:(NSString *)string
+{
+    NSArray * array = [string componentsSeparatedByString:@":"];
+    NSString * str = [NSString stringWithFormat:@"%@.%@",[array objectAtIndex:0],[array objectAtIndex:1]];
+    return [str floatValue];
+}
+- (void)bubbleSort:(NSMutableArray *)array
+{
+    int i, y;
+    BOOL bFinish = YES;
+    for (i = 1; i<= [array count] && bFinish; i++) {
+        bFinish = NO;
+        for (y = (int)[array count]-1; y>=i; y--) {
+            float  num1 = [self getNumberWith:[array objectAtIndex:y]];
+            float num2 = [self getNumberWith:[array objectAtIndex:y-1]];
+            if (num1 < num2) {
+                [array exchangeObjectAtIndex:y-1 withObjectAtIndex:y];
+                [titleArr exchangeObjectAtIndex:y-1 withObjectAtIndex:y];
+                bFinish = YES;
+            }
+        }
+    }
+}
+
+-(void)selfClearKeyAndTitle
+{
+    if ([keyArr count]!=0) {
+        [keyArr removeAllObjects];
+    }
+    if ([titleArr count]!=0) {
+        [titleArr removeAllObjects];
+    }
+
+}
+-(void)scrollViewClearSubView
+{
+    for (UIView * sub in  _scrollView.subviews) {
+        [sub removeFromSuperview];
+    }
+    if ([labels count]!=0) {
+        [labels removeAllObjects];
+    }
+}
+-(void)addLabelForScrollView
+{
+    [self scrollViewClearSubView];
+    
+    for (int index = 0; index<[titleArr count]; index++) {
+        NSString * title = [titleArr objectAtIndex:index];
+        UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 25*index+(_scrollView.size.height/2), _scrollView.size.width, 25)];
+        label.text = title;
+        label.textColor = [UIColor lightGrayColor];
+        label.font = [UIFont systemFontOfSize:14.0f];
+        label.textAlignment = NSTextAlignmentCenter;
+        [_scrollView addSubview:label];
+        [labels addObject:label];
+    }
+}
+-(void)scrollViewMoveLabelWith:(NSString *)string
+{
+    if ([keyArr count]!=0) {
+        int index = 0;
+        BOOL isFinded = NO;
+        for (; index<[keyArr count]; index++) {
+            NSString * key = [keyArr objectAtIndex:index];
+            float  num1 = [self getNumberWith:key];
+            float num2 = [self getNumberWith:string];
+            if (num1 == num2) {
+                isFinded = YES;
+                break;
+            }
+        }
+        if (isFinded) {
+            UILabel * label = [labels objectAtIndex:index];
+            label.textColor = [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+            [_scrollView setContentOffset:CGPointMake(0, 25*index) animated:YES];
+        }
+    }
+
+}
+-(NSString*) parseLrcLine:(NSString *)sourceLineText
+{
+    if (!sourceLineText || sourceLineText.length <= 0)
+        return nil;
+    NSArray *array = [sourceLineText componentsSeparatedByString:@"\n"];
+    for (int i = 0; i < array.count; i++) {
+        NSString *tempStr = [array objectAtIndex:i];
+        NSArray *lineArray = [tempStr componentsSeparatedByString:@"]"];
+        for (int j = 0; j < [lineArray count]-1; j ++) {
+            
+            if ([lineArray[j] length] > 8) {
+                NSString *str1 = [tempStr substringWithRange:NSMakeRange(3, 1)];
+                NSString *str2 = [tempStr substringWithRange:NSMakeRange(6, 1)];
+                if ([str1 isEqualToString:@":"] && [str2 isEqualToString:@"."]) {
+                    NSString *lrcStr = [lineArray lastObject];
+                    NSString *timeStr = [[lineArray objectAtIndex:j] substringWithRange:NSMakeRange(1, 8)];//分割区间求歌词时间
+                    //把时间 和 歌词 加入词典
+                    [keyArr addObject:[timeStr substringToIndex:5]];
+                    [titleArr addObject:lrcStr];
+                }
+            }
+        }
+    }
+    return nil;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect
+{
+    // Drawing code
+}
+*/
+
+@end

+ 21 - 0
FreeMusic/FMMainTableViewCell.h

@@ -0,0 +1,21 @@
+//
+//  FMMainTableViewCell.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "StyledTableViewCell.h"
+@class FMPAImageView;
+@class FMSingerModel;
+
+@interface FMMainTableViewCell : StyledTableViewCell
+{
+    
+    FMPAImageView * headerImageView;
+    UILabel * nameLabel;
+    UILabel * titleLabel;
+    
+}
+@property (nonatomic,strong) FMSingerModel * model;
+@end

+ 61 - 0
FreeMusic/FMMainTableViewCell.m

@@ -0,0 +1,61 @@
+//
+//  FMMainTableViewCell.m
+//  FreeMusic
+//
+//
+
+#import "FMMainTableViewCell.h"
+#import "FMPAImageView.h"
+#import "UIView+Additions.h"
+#import "UIImageView+WebCache.h"
+#import "FMSingerModel.h"
+
+@implementation FMMainTableViewCell
+-(void)setModel:(FMSingerModel *)model_
+{
+    _model = model_;
+    [headerImageView setImageWithURL:[NSURL URLWithString:_model.avatar_big] placeholderImage:[UIImage imageNamed:@"headerImage"]];
+    nameLabel.text = _model.name;
+    if ([_model.company length]!=0) {
+        titleLabel.text = _model.company;
+    }else{
+         titleLabel.text = @"<无信息>";
+    }
+}
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        // Initialization code
+        self.accessoryType =UITableViewCellAccessoryDisclosureIndicator;
+
+        headerImageView = [[FMPAImageView alloc] initWithFrame:CGRectMake(5, 5, 60, 60)];
+        [self.contentView addSubview:headerImageView];
+        
+        nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(headerImageView.size.width+headerImageView.origin.x+5, headerImageView.origin.y, self.contentView.size.width-headerImageView.size.width-headerImageView.origin.x-30, 30)];
+        nameLabel.font = [UIFont boldSystemFontOfSize:16.0f];
+        [self.contentView addSubview:nameLabel];
+        
+        titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(nameLabel.origin.x, nameLabel.size.height+nameLabel.origin.y+5, nameLabel.size.width, nameLabel.size.height-10)];
+        titleLabel.font = [UIFont systemFontOfSize:13.0f];
+        titleLabel.textColor = [UIColor lightGrayColor];
+
+        [self.contentView addSubview:titleLabel];
+    }
+    return self;
+}
+
+
+- (void)awakeFromNib
+{
+    // Initialization code
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated
+{
+    [super setSelected:selected animated:animated];
+
+    // Configure the view for the selected state
+}
+
+@end

+ 16 - 0
FreeMusic/FMMainViewController.h

@@ -0,0 +1,16 @@
+//
+//  FMMainViewController.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "FMBaseViewController.h"
+@interface FMMainViewController : FMBaseViewController<UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate>
+{
+    UITableView * _tableView;
+
+}
+@property (strong,nonatomic) UISearchBar *searchBar;
+
+@end

+ 159 - 0
FreeMusic/FMMainViewController.m

@@ -0,0 +1,159 @@
+//
+//  FMMainViewController.m
+//  FreeMusic
+//
+//
+
+#import "FMMainViewController.h"
+#import "FMMainTableViewCell.h"
+#import "FMSingerSongListViewController.h"
+#import "FMMusicViewController.h"
+@interface FMMainViewController ()
+{
+    NSMutableArray * array;
+}
+@end
+
+@implementation FMMainViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        // Custom initialization
+    }
+    return self;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    [self.navigationController setNavigationBarHidden:YES animated:NO];
+    self.view.backgroundColor = [UIColor whiteColor];
+    
+    self.navigation.leftImage  =nil;// [UIImage imageNamed:@"nav_backbtn.png"];
+    self.navigation.rightImage = [UIImage imageNamed:@"nav_music.png"];
+    self.navigation.title = @"心情音乐";
+    self.navigation.navigaionBackColor = [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+
+
+    array = [NSMutableArray new];
+    
+    FMSingerModel * model = [FMSingerModel new];
+    array = [model itemTop100];
+
+    self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0f,self.navigation.size.height+self.navigation.origin.y, self.view.size.width, 44.0f)];
+    _searchBar.delegate =self;
+    _searchBar.placeholder = @"搜索:歌手";
+//	_searchBar.tintColor = [UIColor lightGrayColor];
+    _searchBar.showsCancelButton = YES;
+	_searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
+	_searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
+	_searchBar.keyboardType = UIKeyboardTypeDefault;
+	// Create the search display controller
+    
+	_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _searchBar.size.height+_searchBar.origin.y, self.view.size.width, self.view.size.height-self.navigation.size.height-self.searchBar.size.height-48.5f)];
+    _tableView.delegate =self;
+    _tableView.dataSource = self;
+    _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+	[self.view addSubview:_tableView];
+	// Do any additional setup after loading the view.
+    [self.view addSubview:_searchBar];
+
+    // Do any additional setup after loading the view.
+}
+-(void)rightButtonClickEvent
+{
+    if (globle.isPlaying) {
+        FMMusicViewController * pushController = [FMMusicViewController shareMusicViewController];
+        [self.navigationController pushViewController:pushController animated:YES];
+    }
+}
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+	[_searchBar resignFirstResponder];
+    _searchBar.text = @"";
+    FMSingerModel * model = [FMSingerModel new];
+    array = [model itemTop100];
+    [_tableView reloadData];
+}
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+    // 键盘输入回车时进行响应
+    [_searchBar resignFirstResponder];
+    
+    if ([array count]!=0) {
+        [array removeAllObjects];
+    }
+    [ProgressHUD show:nil];
+    [self getSingerData];
+}
+-(void)getSingerData
+{
+    FMSingerModel * model = [FMSingerModel new];
+    array = [model itemWith:_searchBar.text];
+    [_tableView reloadData];
+    [ProgressHUD dismiss];
+    NSIndexPath * indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
+    [_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+//#warning Potentially incomplete method implementation.
+    // Return the number of sections.
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+//#warning Incomplete method implementation.
+    // Return the number of rows in the section.
+    return [array count];
+}
+
+
+ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+ {
+     NSString * dentifier = @"cell";
+        FMMainTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:dentifier];
+     if (cell == nil) {
+         cell = [[FMMainTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:dentifier];
+     }
+     FMSingerModel * model = [array objectAtIndex:indexPath.row];
+     cell.model = model;
+ return cell;
+ }
+
+-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return 70;
+}
+-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    FMSingerModel * model = [array objectAtIndex:indexPath.row];
+    FMSingerSongListViewController * pushController = [[FMSingerSongListViewController alloc] init];
+    pushController.singerModel = model;
+    [self.navigationController pushViewController:pushController animated:YES];
+}
+
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+    // Dispose of any resources that can be recreated.
+}
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
+{
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 23 - 0
FreeMusic/FMMusicViewController.h

@@ -0,0 +1,23 @@
+//
+//  FMMusicViewController.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "FMBaseViewController.h"
+
+@class FMSongListModel,FMSongModel;
+
+@interface FMMusicViewController : FMBaseViewController<UIAlertViewDelegate>
+{
+    FMSongModel * _songModel;
+}
+
+@property (nonatomic,strong) FMSongListModel * songListModel;
+@property (nonatomic,strong) NSMutableArray * array;
+@property (nonatomic,assign) NSInteger index;
+@property (nonatomic,assign) BOOL only;
++(FMMusicViewController *)shareMusicViewController;
+-(void)playMusicWithSongLink:(FMSongListModel*)model;
+@end

+ 289 - 0
FreeMusic/FMMusicViewController.m

@@ -0,0 +1,289 @@
+//
+//  FMMusicViewController.m
+//  FreeMusic
+//
+//
+
+#import "FMMusicViewController.h"
+#import "RFRadioView.h"
+#import "FSPlaylistItem.h"
+#import "MCDataEngine.h"
+#import "FMSongListModel.h"
+#import "FMSongModel.h"
+#import <MediaPlayer/MediaPlayer.h>
+#import "FMPlayingListViewController.h"
+#import "DBDaoHelper.h"
+#import "SelectTypeViewController.h"
+@interface FMMusicViewController ()<RFRadioViewDelegate,SelectTypeViewControllerDelegate>
+{
+    RFRadioView * _radioView;
+}
+@end
+
+@implementation FMMusicViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        // Custom initialization
+    }
+    return self;
+}
++(FMMusicViewController *)shareMusicViewController
+{
+    static FMMusicViewController * musicViewController;
+    if (musicViewController == nil) {
+        musicViewController = [[FMMusicViewController alloc] init];
+    }
+    return musicViewController;
+}
+-(void)viewWillAppear:(BOOL)animated
+{
+    [super viewWillAppear:animated];
+    [self.tabBarController.tabBar setHidden:YES];
+}
+-(void)viewWillDisappear:(BOOL)animated
+{
+    [super viewWillDisappear:animated];
+    [self.tabBarController.tabBar setHidden:NO];
+
+}
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    [self.navigationController setNavigationBarHidden:YES animated:NO];
+    self.view.backgroundColor = [UIColor whiteColor];
+    self.navigation.leftImage  = [UIImage imageNamed:@"nav_backbtn.png"];
+//    self.navigation.headerImage = [UIImage imageNamed:@"nav_canadaicon.png"];
+//    self.navigation.title = [NSString stringWithFormat:@"%@(歌曲%d)",_singerModel.name,_singerModel.songs_total];
+    self.navigation.navigaionBackColor =  [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+    
+    _songModel = [FMSongModel new];
+    
+    _radioView = [[RFRadioView alloc] initWithFrame:CGRectMake(0,self.navigation.size.height+self.navigation.origin.y, 320,self.view.size.height-self.navigation.size.height-self.navigation.origin.y)];
+    _radioView.delegate = self;
+    _radioView.only = self.only;
+    [self.view addSubview:_radioView];
+    
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackgroundSetSongInformation:) name:FMRFRadioViewSetSongInformationNotification object:nil];
+}
+
+-(BOOL)isLocalMP3
+{
+    NSFileManager * manager = [[NSFileManager alloc] init];
+    NSString *filePath = [DocumentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%lld/%lld/%lld.mp3",_songListModel.ting_uid,_songListModel.song_id,_songListModel.song_id]];
+    if ([manager fileExistsAtPath:filePath]) {
+        return YES;
+    }
+    return NO;
+}
+-(NSString *)localMP3path
+{
+    NSString *filePath = [DocumentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%lld/%lld/%lld.mp3",_songListModel.ting_uid,_songListModel.song_id,_songListModel.song_id]];
+    return filePath;
+}
+-(void)playMusicWithSongLink:(FMSongListModel*)model
+{
+    _radioView.only = self.only;
+    NSLog(@"%@",model.title);
+        MCDataEngine * network = [MCDataEngine new];
+        [network getSongInformationWith:model.song_id WithCompletionHandler:^(FMSongModel *songModel) {
+            FSPlaylistItem * item = [[FSPlaylistItem alloc] init];
+            item.title = songModel.songName;
+            item.url = songModel.songLink;
+            _radioView.songlistModel = model;
+            _radioView.songModel = songModel;
+            [_radioView setRadioViewLrc];
+            globle.isPlaying = YES;
+            _songModel = songModel;
+            _songListModel =model;
+            self.navigation.title =songModel.songName;
+            _radioView.selectedPlaylistItem = item;
+            if (globle.isApplicationEnterBackground) {
+                [self applicationDidEnterBackgroundSetSongInformation:nil];
+            }
+        } errorHandler:^(NSError *error) {
+            if ([self isLocalMP3]) {
+                NSLog(@"isLocal");
+                [self playModleIsLock];
+                [ProgressHUD showError:@"网络错误"];
+//                FSPlaylistItem * item = [[FSPlaylistItem alloc] init];
+//                item.title = _songListModel.title;
+//                item.url = [self localMP3path];
+//                _radioView.songlistModel = model;
+//                [_radioView setRadioViewLrc];
+//                globle.isPlaying = YES;
+//                _songListModel =model;
+//                self.navigation.title =_songListModel.title;
+//                _radioView.selectedPlaylistItem = item;
+            }
+        }];
+}
+-(void)applicationDidEnterBackgroundSetSongInformation:(NSNotification *)notification
+{
+    if (NSClassFromString(@"MPNowPlayingInfoCenter")) {
+		NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
+		[dict setObject:_songModel.songName forKey:MPMediaItemPropertyTitle];
+		[dict setObject:_songListModel.author  forKey:MPMediaItemPropertyArtist];
+//		[dict setObject:[[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"headerImage.png"]] forKey:MPMediaItemPropertyArtwork];
+		[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
+		[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
+	}
+}
+
+-(void)playModleIsLock
+{
+    FSPlaylistItem * item = [[FSPlaylistItem alloc] init];
+    item.title = _songModel.songName;
+    item.url = _songModel.songLink;
+    _radioView.selectedPlaylistItem = item;
+    _radioView.songlistModel = _songListModel;
+    _radioView.songModel = _songModel;
+    globle.isPlaying = YES;
+    self.navigation.title =_songModel.songName;
+}
+#pragma
+#pragma mark RFRadioViewDelegate
+-(void)radioView:(RFRadioView *)view musicStop:(NSInteger)playModel
+{
+    FMSongListModel * model = nil;
+    switch (playModel) {
+        case 0:
+        {
+            self.index+=1;
+            if (self.index>=[self.array count]) {
+                self.index = 0;
+            }
+            model = [self.array objectAtIndex:self.index];
+            [self playMusicWithSongLink:model];
+        }
+            break;
+        case 1:
+        {
+            NSInteger  number = [self.array count];
+            model = [self.array objectAtIndex:arc4random()%number];
+            [self playMusicWithSongLink:model];
+        }
+            break;
+        case -1:
+        {
+            model = self.songListModel;
+            [self playModleIsLock];
+        }
+            break;
+        default:
+            break;
+    }
+}
+-(void)radioView:(RFRadioView *)view preSwitchMusic:(UIButton *)pre
+{
+    if ([self.array count]!=0) {
+        self.index-=1;
+        if (self.index<0) {
+            self.index = 0;
+        }
+        FMSongListModel * model = [self.array objectAtIndex:self.index];
+        [self playMusicWithSongLink:model];
+    }
+}
+-(void)radioView:(RFRadioView *)view nextSwitchMusic:(UIButton *)next
+{
+    if ([self.array count]!=0) {
+        self.index+=1;
+        if (self.index==[self.array count]) {
+            self.index = self.index-1;
+        }
+        FMSongListModel * model = [self.array objectAtIndex:self.index];
+        [self playMusicWithSongLink:model];
+    }
+}
+-(void)radioView:(RFRadioView *)view playListButton:(UIButton *)btn
+{
+/*    FMPlayingListViewController * viewController = [[FMPlayingListViewController alloc] init];
+    UINavigationController * navigation = [[UINavigationController alloc] initWithRootViewController:viewController];
+    [self.navigationController presentViewController:navigation animated:YES completion:^{
+        
+    }];*/
+    //列表功能无法实现
+}
+-(void)radioView:(RFRadioView *)view downLoadButton:(UIButton *)btn
+{
+    MCDataEngine * network = [MCDataEngine new];
+    
+    [network downLoadSongWith:_songListModel.ting_uid :_songListModel.song_id :view.selectedPlaylistItem.url WithCompletionHandler:^(BOOL sucess, NSString *path) {
+        [DBDaoHelper insertDownMusicWith:_songListModel type:1];
+        _radioView.downLoadButton.enabled = NO;
+    } errorHandler:^(NSError *error) {
+        _radioView.downLoadButton.enabled = YES;
+    }];
+}
+-(void)radioView:(RFRadioView *)view collectButton:(UIButton *)btn{
+    SelectTypeViewController *vc = [[SelectTypeViewController alloc]init];
+    vc.delegate = self;
+    [self.navigationController pushViewController:vc animated:YES];
+    
+//    NSArray *array = [DBDaoHelper myType];
+//    NSMutableString *str = [[NSMutableString alloc]init];
+//    for (int i = 0; i<array.count; i++) {
+//        [str appendString:@"@\""];
+//        [str appendFormat:@"%@",[array objectAtIndex:i]];
+//        [str appendString:@"\","];
+//    }
+//    [str appendString:@"nil"];
+//    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:str,nil];
+//    [alert show];
+    
+//    MCDataEngine * network = [MCDataEngine new];
+//    array = [NSArray arrayWithObjects:@"欢快",@"甜蜜" ,@"安静",@"伤感",@"思念",nil];
+//    [network downLoadSongWith:_songListModel.ting_uid :_songListModel.song_id :view.selectedPlaylistItem.url WithCompletionHandler:^(BOOL sucess, NSString *path) {
+//        _radioView.downLoadButton.enabled = NO;
+//    } errorHandler:^(NSError *error) {
+//        _radioView.downLoadButton.enabled = YES;
+//    }];
+}
+-(void)selectTypeViewController:(SelectTypeViewController *)vc selectType:(NSInteger)type{
+    BOOL result = [DBDaoHelper insertMusicWith:_songListModel type:(type+1)];
+    if (result) {
+        UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:@"标记成功" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
+        [alert show];
+    }else{
+        UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:@"您好像已经标记了" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
+        [alert show];
+    }
+}
+-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
+    NSLog(@"%@",_songListModel.title);
+    if (buttonIndex == 0) {
+        
+    }else{
+       BOOL result = [DBDaoHelper insertMusicWith:_songListModel type:buttonIndex];
+        if (result) {
+            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:@"标记成功" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
+            [alert show];
+        }else{
+            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:@"您好像已经标记了" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
+            [alert show];
+        }
+    }
+
+}
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+    // Dispose of any resources that can be recreated.
+}
+
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
+{
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 17 - 0
FreeMusic/FMMySongModel.h

@@ -0,0 +1,17 @@
+//
+//  FMMySongModel.h
+//  FreeMusic
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface FMMySongModel : NSObject
+@property (nonatomic,assign) long long ting_uid;
+@property (nonatomic,assign) long long song_id;
+@property (nonatomic,copy) NSString * songName;
+@property (nonatomic,copy) NSString * lrcLink;
+@property (nonatomic,copy) NSString * songLink;
+@property (nonatomic,copy) NSString * songPicBig;
+@property (nonatomic,assign) long long albumid;
+@end

+ 24 - 0
FreeMusic/FMMySongModel.m

@@ -0,0 +1,24 @@
+//
+//  FMMySongModel.m
+//  FreeMusic
+//
+//
+
+#import "FMMySongModel.h"
+
+@implementation FMMySongModel
+//表名
++(NSString *)getTableName
+{
+    return @"FMMySong";
+}
+//表版本
++(int)getTableVersion
+{
+    return 1;
+}
++(NSString *)getPrimaryKey
+{
+    return @"song_id";
+}
+@end

+ 15 - 0
FreeMusic/FMPAImageView.h

@@ -0,0 +1,15 @@
+//
+//  SPMImageAsyncView.h
+//  ImageDL
+//
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FMPAImageView : UIImageView
+
+@property (nonatomic, assign, getter = isCacheEnabled) BOOL cacheEnabled;
+@property (nonatomic, strong) UIImageView *containerImageView;
+
+- (id)initWithFrame:(CGRect)frame backgroundProgressColor:(UIColor *)backgroundProgresscolor;
+@end

+ 63 - 0
FreeMusic/FMPAImageView.m

@@ -0,0 +1,63 @@
+//
+//  SPMImageAsyncView.m
+//  ImageDL
+//
+//
+
+#import "FMPAImageView.h"
+
+#pragma mark - Utils
+
+#define rad(degrees) ((degrees) / (180.0 / M_PI))
+#define kLineWidth 3.f
+
+NSString * const spm_identifier = @"spm.imagecache.tg";
+
+#pragma mark - SPMImageAsyncView interface
+
+@interface FMPAImageView ()
+
+@property (nonatomic, strong) CAShapeLayer *backgroundLayer;
+@property (nonatomic, strong) CAShapeLayer *progressLayer;
+
+@property (nonatomic, strong) UIView      *progressContainer;
+
+@end
+
+#pragma mark - SPMImageAsyncView
+
+
+@implementation FMPAImageView
+
+- (id)initWithFrame:(CGRect)frame {
+    return [[FMPAImageView alloc] initWithFrame:frame
+                      backgroundProgressColor:[UIColor whiteColor]];
+}
+
+- (id)initWithFrame:(CGRect)frame backgroundProgressColor:(UIColor *)backgroundProgresscolor
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        
+        self.layer.cornerRadius     = CGRectGetWidth(self.bounds)/2.f;
+        self.layer.masksToBounds    = NO;
+        self.clipsToBounds          = YES;
+        
+        CGPoint arcCenter           = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
+        CGFloat radius              = MIN(CGRectGetMidX(self.bounds)-1, CGRectGetMidY(self.bounds)-1);
+        
+        UIBezierPath *circlePath    = [UIBezierPath bezierPathWithArcCenter:arcCenter
+                                                                     radius:radius
+                                                                 startAngle:-rad(90)
+                                                                   endAngle:rad(360-90)
+                                                                  clockwise:YES];
+        
+        _backgroundLayer = [CAShapeLayer layer];
+        _backgroundLayer.path           = circlePath.CGPath;
+        _backgroundLayer.strokeColor    = [backgroundProgresscolor CGColor];
+        _backgroundLayer.fillColor      = [[UIColor clearColor] CGColor];
+        _backgroundLayer.lineWidth      = kLineWidth;
+    }
+    return self;
+}
+@end

+ 17 - 0
FreeMusic/FMPlayingListViewController.h

@@ -0,0 +1,17 @@
+//
+//  FMPlayingListViewController.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "FMBaseViewController.h"
+
+@class FMSongListModel;
+
+@interface FMPlayingListViewController : UIViewController
+{
+    UITableView * _tableView;
+}
+@property (nonatomic,strong) NSMutableArray * array;
+@end

+ 56 - 0
FreeMusic/FMPlayingListViewController.m

@@ -0,0 +1,56 @@
+//
+//  FMPlayingListViewController.m
+//  FreeMusic
+//
+//
+
+#import "FMPlayingListViewController.h"
+
+@interface FMPlayingListViewController ()
+
+@end
+
+@implementation FMPlayingListViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        // Custom initialization
+    }
+    return self;
+}
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    self.view.backgroundColor = [UIColor whiteColor];
+    UIBarButtonItem * done = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(doneEvent)];
+    self.navigationItem.rightBarButtonItem = done;
+    // Do any additional setup after loading the view.
+}
+
+-(void)doneEvent
+{
+    [self.navigationController dismissViewControllerAnimated:YES completion:^{
+        
+    }];
+}
+
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+    // Dispose of any resources that can be recreated.
+}
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
+{
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 38 - 0
FreeMusic/FMSingerModel.h

@@ -0,0 +1,38 @@
+//
+//  FMSingerModel.h
+//  FreeMusic
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface FMSingerModel : NSObject
+@property (nonatomic,assign) long long  ting_uid;
+@property (nonatomic,copy) NSString * name;
+@property (nonatomic,copy) NSString * firstchar;
+@property (nonatomic,assign) int gender;
+@property (nonatomic) int area;
+@property (nonatomic,copy) NSString * country;
+@property (nonatomic,copy) NSString * avatar_big;
+@property (nonatomic,copy) NSString * avatar_middle;
+@property (nonatomic,copy) NSString * avatar_small;
+@property (nonatomic,copy) NSString * avatar_mini;
+@property (nonatomic,copy) NSString * constellation;
+@property (nonatomic,assign) float stature;
+@property (nonatomic,assign) float weight;
+@property (nonatomic,copy) NSString * bloodtype;
+@property (nonatomic,copy) NSString * company;
+@property (nonatomic,copy) NSString * intro;
+@property (nonatomic,assign) int albums_total;
+@property (nonatomic,assign) int songs_total;
+@property (nonatomic,assign) NSDate * birth;
+@property (nonatomic,copy) NSString * url;
+@property (nonatomic,assign) int artist_id;
+@property (nonatomic,copy) NSString * avatar_s180;
+@property (nonatomic,copy) NSString * avatar_s500;
+@property (nonatomic,copy) NSString * avatar_s1000;
+@property (nonatomic,assign) int piao_id;
+
+-(NSMutableArray *)itemWith:(NSString *)name;
+-(NSMutableArray *)itemTop100;
+@end

+ 47 - 0
FreeMusic/FMSingerModel.m

@@ -0,0 +1,47 @@
+//
+//  FMSingerModel.m
+//  FreeMusic
+//
+//
+
+#import "FMSingerModel.h"
+
+@implementation FMSingerModel
+//表名
++(NSString *)getTableName
+{
+    return @"FMSongerInfor";
+}
+//表版本
++(int)getTableVersion
+{
+    return 1;
+}
++(NSString *)getPrimaryKey
+{
+    return @"ting_uid";
+}
+-(NSMutableArray *)itemWith:(NSString *)name
+{
+    NSMutableArray * array = [FMSingerModel searchWithWhere:[NSString stringWithFormat:@"name like '%%%@%%'",name] orderBy:nil offset:0 count:0];
+    NSMutableArray * temp = [NSMutableArray new];
+    NSMutableArray * temp1 = [NSMutableArray new];
+    for (FMSingerModel * sub in array) {
+        if ([sub.name length]!=0) {
+            if ([sub.company length]!=0) {
+                [temp addObject:sub];
+            }else{
+                [temp1 addObject:sub];
+            }
+        }
+    }
+    [temp addObjectsFromArray:temp1];
+    return temp;
+}
+-(NSMutableArray *)itemTop100
+{
+//    NSMutableArray * array = [FMSingerModel searchWithWhere:[NSString stringWithFormat:@"'songs_total' >1000"] orderBy:nil offset:0 count:50];
+    return [self itemWith:@"李"];
+}
+
+@end

+ 18 - 0
FreeMusic/FMSingerSongListViewController.h

@@ -0,0 +1,18 @@
+//
+//  FMSingerDetailViewController.h
+//  FreeMusic
+//
+//
+
+#import <UIKit/UIKit.h>
+#import "FMBaseViewController.h"
+
+@class FMSingerModel;
+
+@interface FMSingerSongListViewController : FMBaseViewController<UITableViewDelegate,UITableViewDataSource>
+{
+    UITableView * _tableView;
+}
+@property (nonatomic,strong) FMSingerModel * singerModel;
+
+@end

+ 201 - 0
FreeMusic/FMSingerSongListViewController.m

@@ -0,0 +1,201 @@
+//
+//  FMSingerDetailViewController.m
+//  FreeMusic
+//
+//
+
+#import "FMSingerSongListViewController.h"
+#import "FMLoadMoreFooterView.h"
+#import "FMMusicViewController.h"
+#import "NSDate+Additions.h"
+#import "FMSongListTableViewCell.h"
+
+@interface FMSingerSongListViewController ()<UIScrollViewDelegate>
+{
+    NSMutableArray * array ;
+    BOOL isLoadingMore;
+    BOOL isCanLoadMore;
+    FMLoadMoreFooterView * footerView;
+    int song_total;
+}
+@end
+
+@implementation FMSingerSongListViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    if (self) {
+        // Custom initialization
+    }
+    return self;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    [self.navigationController setNavigationBarHidden:YES animated:NO];
+    self.view.backgroundColor = [UIColor whiteColor];
+    self.navigation.leftImage  = [UIImage imageNamed:@"nav_backbtn.png"];
+    //    self.navigation.headerImage = [UIImage imageNamed:@"nav_canadaicon.png"];
+    self.navigation.rightImage = [UIImage imageNamed:@"nav_music.png"];
+    self.navigation.title = [NSString stringWithFormat:@"%@(歌曲%d)",_singerModel.name,_singerModel.songs_total];
+    self.navigation.navigaionBackColor =  [UIColor colorWithRed:192.0f/255.0f green:37.0f/255.0f blue:62.0f/255.0f alpha:1.0f];
+    
+    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.navigation.size.height+self.navigation.origin.y, self.view.size.width, self.view.size.height-self.navigation.size.height-48.5f)];
+    _tableView.delegate =self;
+    _tableView.dataSource = self;
+    _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+	[self.view addSubview:_tableView];
+    
+    
+    if (_singerModel.songs_total<20) {
+        song_total = _singerModel.songs_total;
+        isLoadingMore = NO;
+    }else{
+        song_total = 20;
+        isLoadingMore = YES;
+    }
+    if (isLoadingMore) {
+        footerView = [[FMLoadMoreFooterView alloc] initWithFrame:CGRectMake(0, 0, _tableView.size.width, 70)];
+        _tableView.tableFooterView = footerView;
+    }
+
+    [ProgressHUD show:nil];
+    [self getSongsList];
+    // Do any additional setup after loading the view.
+}
+
+-(void)rightButtonClickEvent
+{
+    if (globle.isPlaying) {
+        FMMusicViewController * pushController = [FMMusicViewController shareMusicViewController];
+        pushController.only = NO;
+        [self.navigationController pushViewController:pushController animated:YES];
+    }
+}
+-(void)getSongsList
+{
+    MCDataEngine * network = [MCDataEngine new];
+    [network getSingerSongListWith:_singerModel.ting_uid :song_total WithCompletionHandler:^(FMSongList *songList) {
+        array = songList.songLists;
+        [_tableView reloadData];
+        [ProgressHUD dismiss];
+    } errorHandler:^(NSError *error) {
+        
+    }];
+}
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    //#warning Potentially incomplete method implementation.
+    // Return the number of sections.
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    //#warning Incomplete method implementation.
+    // Return the number of rows in the section.
+    return [array count];
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSString * dentifier = @"cell";
+    FMSongListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:dentifier];
+    if (cell == nil) {
+        cell = [[FMSongListTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:dentifier];
+    }
+    FMSongListModel * model = [array objectAtIndex:indexPath.row];
+    cell.model = model;
+    return cell;
+}
+-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return 60;
+}
+-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    FMSongListModel * model = [array objectAtIndex:indexPath.row];
+    FMMusicViewController * pushController = [FMMusicViewController shareMusicViewController];
+    pushController.songListModel = model;
+    pushController.array = array;
+    pushController.index = indexPath.row;
+    pushController.only = NO;
+    [pushController playMusicWithSongLink:model];
+    [self.navigationController pushViewController:pushController animated:YES];
+}
+
+-(void)loadMore
+{
+    if (isLoadingMore) {
+        [footerView.activeView startAnimating];
+        song_total+=20;
+        [self performSelector:@selector(loadMoreData) withObject:nil afterDelay:0.1];
+        isLoadingMore = NO;
+        footerView.titleLabel.text = @"获取中...";
+    }
+}
+-(void)loadMoreData
+{
+    MCDataEngine * network = [MCDataEngine new];
+    [network getSingerSongListWith:_singerModel.ting_uid :song_total WithCompletionHandler:^(FMSongList *songList) {
+        
+        array = songList.songLists;
+        [_tableView reloadData];
+        if (song_total> _singerModel.songs_total){
+            song_total = _singerModel.songs_total;
+            isCanLoadMore = YES; // signal that there won't be any more items to load
+        }else{
+            isCanLoadMore = NO;
+        }
+        [self loadMoreCompleted];
+    } errorHandler:^(NSError *error) {
+        
+    }];
+}
+
+-(void)loadMoreCompleted
+{
+    isLoadingMore = YES;
+    if (isCanLoadMore) {
+        _tableView.tableFooterView = nil;
+    }else{
+        [footerView.activeView stopAnimating];
+    }
+}
+
+#define DEFAULT_HEIGHT_OFFSET 44.0f
+
+#pragma mark UIScrollViewDelegate
+- (void) scrollViewDidScroll:(UIScrollView *)scrollView
+{
+    if (isLoadingMore && !isCanLoadMore) {
+        CGFloat scrollPosition = scrollView.contentSize.height - scrollView.frame.size.height - scrollView.contentOffset.y;
+        if (scrollPosition < DEFAULT_HEIGHT_OFFSET) {
+            [self loadMore];
+        }
+    }
+}
+
+- (void)didReceiveMemoryWarning
+{
+    [super didReceiveMemoryWarning];
+    // Dispose of any resources that can be recreated.
+}
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
+{
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 51 - 0
FreeMusic/FMSongListModel.h

@@ -0,0 +1,51 @@
+//
+//  FMSongListModel.h
+//  FreeMusic
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface FMSongListModel : NSObject
+@property (nonatomic,assign) int artist_id;
+@property (nonatomic,assign) int all_artist_ting_uid;
+@property (nonatomic,assign) int all_artist_id;
+@property (nonatomic,copy) NSString * language;
+@property (nonatomic,copy) NSString * publishtime;
+@property (nonatomic,assign) int album_no;
+@property (nonatomic,copy) NSString * pic_big;
+@property (nonatomic,copy) NSString * pic_small;
+@property (nonatomic,copy) NSString *country;
+@property (nonatomic,assign) int  area;
+@property (nonatomic,copy) NSString *lrclink;
+@property (nonatomic,assign) int hot;
+@property (nonatomic,assign) int file_duration;
+@property (nonatomic,assign) int del_status;
+@property (nonatomic,assign) int resource_type;
+@property (nonatomic,assign) int copy_type;
+@property (nonatomic,assign) int relate_status;
+@property (nonatomic,assign) int all_rate;
+@property (nonatomic,assign) int has_mv_mobile;
+@property (nonatomic,assign) long long toneid;
+@property (nonatomic,assign) long long song_id;
+@property (nonatomic,copy) NSString * title;
+@property (nonatomic,assign) long long ting_uid;
+@property (nonatomic,copy) NSString * author;
+@property (nonatomic,assign) long long album_id;
+@property (nonatomic,copy) NSString * album_title;
+@property (nonatomic,assign) int  is_first_publish;
+@property (nonatomic,assign) int  havehigh;
+@property (nonatomic,assign) int charge;
+@property (nonatomic,assign) int  has_mv;
+@property (nonatomic,assign) int  learn;
+@property (nonatomic,assign) int  piao_id;
+@property (nonatomic,assign) long long listen_total;
+@property (nonatomic,copy) NSString * name;
+@end
+
+@interface FMSongList : NSObject
+@property (nonatomic,assign) int songnums;
+@property (nonatomic,assign) BOOL havemore;
+@property (nonatomic,assign) int error_code;
+@property (nonatomic,retain) NSMutableArray * songLists;
+@end

+ 34 - 0
FreeMusic/FMSongListModel.m

@@ -0,0 +1,34 @@
+//
+//  FMSongListModel.m
+//  FreeMusic
+//
+//
+
+#import "FMSongListModel.h"
+
+@implementation FMSongListModel
+//表名
++(NSString *)getTableName
+{
+    return @"FMSongListTable";
+}
+//表版本
++(int)getTableVersion
+{
+    return 1;
+}
++(NSString *)getPrimaryKey
+{
+    return @"song_id";
+}
+@end
+
+@implementation FMSongList
+-(id)init
+{
+    if (self = [super init]) {
+        self.songLists = [NSMutableArray new];
+    }
+    return self;
+}
+@end

+ 18 - 0
FreeMusic/FMSongListTableViewCell.h

@@ -0,0 +1,18 @@
+//
+//  FMSongListTableViewCell.h
+//  FreeMusic
+//
+//
+
+#import "StyledTableViewCell.h"
+
+@class FMSongListModel;
+
+@interface FMSongListTableViewCell : StyledTableViewCell
+{
+    UILabel * nameLabel;
+    UILabel * titleLabel;
+    UILabel * timeLabel;
+}
+@property (nonatomic,strong) FMSongListModel * model;
+@end

+ 68 - 0
FreeMusic/FMSongListTableViewCell.m

@@ -0,0 +1,68 @@
+//
+//  FMSongListTableViewCell.m
+//  FreeMusic
+//
+//
+
+#import "FMSongListTableViewCell.h"
+#import "UIView+Additions.h"
+#import "FMSongListModel.h"
+
+@implementation FMSongListTableViewCell
+-(NSString*)TimeformatFromSeconds:(int)seconds
+{
+    int totalm = seconds/(60);
+    int h = totalm/(60);
+    int m = totalm%(60);
+    int s = seconds%(60);
+    if (h==0) {
+        return  [[NSString stringWithFormat:@"%02d:%02d", m, s] substringToIndex:5];
+    }
+    return [NSString stringWithFormat:@"%02d:%02d:%02d", h, m, s];
+}
+
+-(void)setModel:(FMSongListModel *)model_
+{
+    _model = model_;
+    nameLabel.text = _model.title;
+    timeLabel.text =[self TimeformatFromSeconds:_model.file_duration];
+    titleLabel.text = [NSString stringWithFormat:@"%@•%@",_model.author,_model.album_title];
+}
+
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        self.accessoryType =UITableViewCellAccessoryDisclosureIndicator;
+
+        // Initialization code
+        nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 5, self.contentView.size.width-16-60, 25)];
+        nameLabel.font = [UIFont systemFontOfSize:16.0f];
+//        nameLabel.backgroundColor = [UIColor redColor];
+        [self.contentView addSubview:nameLabel];
+        
+        timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(nameLabel.size.width+nameLabel.origin.x, nameLabel.size.height/2+nameLabel.origin.y, 40, 25)];
+        timeLabel.font = [UIFont systemFontOfSize:14.0f];
+//        timeLabel.backgroundColor = [UIColor blueColor];
+        timeLabel.textColor = [UIColor lightGrayColor];
+        [self.contentView addSubview:timeLabel];
+        
+        titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(nameLabel.origin.x, nameLabel.size.height+nameLabel.origin.y, nameLabel.size.width, 25)];
+        titleLabel.font = [UIFont systemFontOfSize:14.0f];
+//        titleLabel.backgroundColor = [UIColor greenColor];
+        titleLabel.textColor = [UIColor lightGrayColor];
+        [self.contentView addSubview:titleLabel];
+    }
+    return self;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect
+{
+    // Drawing code
+}
+*/
+
+@end

+ 0 - 0
FreeMusic/FMSongModel.h


Some files were not shown because too many files changed in this diff