FSAudioStream.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /*
  2. * This file is part of the FreeStreamer project,
  3. * (C)Copyright 2011-2013 Matias Muhonen.
  4. * See the file ''LICENSE'' for using the code.
  5. */
  6. #import "FSAudioStream.h"
  7. #import "Reachability.h"
  8. #include "audio_stream.h"
  9. #import <AVFoundation/AVFoundation.h>
  10. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  11. #import <AudioToolbox/AudioToolbox.h>
  12. #endif
  13. NSString* const FSAudioStreamStateChangeNotification = @"FSAudioStreamStateChangeNotification";
  14. NSString* const FSAudioStreamNotificationKey_Stream = @"stream";
  15. NSString* const FSAudioStreamNotificationKey_State = @"state";
  16. NSString* const FSAudioStreamErrorNotification = @"FSAudioStreamErrorNotification";
  17. NSString* const FSAudioStreamNotificationKey_Error = @"error";
  18. NSString* const FSAudioStreamMetaDataNotification = @"FSAudioStreamMetaDataNotification";
  19. NSString* const FSAudioStreamNotificationKey_MetaData = @"metadata";
  20. class AudioStreamStateObserver : public astreamer::Audio_Stream_Delegate
  21. {
  22. private:
  23. bool m_eofReached;
  24. public:
  25. astreamer::Audio_Stream *source;
  26. FSAudioStreamPrivate *priv;
  27. void audioStreamErrorOccurred(int errorCode);
  28. void audioStreamStateChanged(astreamer::Audio_Stream::State state);
  29. void audioStreamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData);
  30. };
  31. /*
  32. * ===============================================================
  33. * FSAudioStream private implementation
  34. * ===============================================================
  35. */
  36. @interface FSAudioStreamPrivate : NSObject {
  37. astreamer::Audio_Stream *_audioStream;
  38. NSURL *_url;
  39. BOOL _strictContentTypeChecking;
  40. AudioStreamStateObserver *_observer;
  41. BOOL _wasInterrupted;
  42. BOOL _wasDisconnected;
  43. NSString *_defaultContentType;
  44. Reachability *_reachability;
  45. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  46. UIBackgroundTaskIdentifier _backgroundTask;
  47. #endif
  48. }
  49. @property (nonatomic,assign) NSURL *url;
  50. @property (nonatomic,assign) BOOL strictContentTypeChecking;
  51. @property (nonatomic,assign) NSString *defaultContentType;
  52. @property (nonatomic,assign) BOOL wasInterrupted;
  53. @property (copy) void (^onCompletion)();
  54. @property (copy) void (^onFailure)();
  55. - (void)reachabilityChanged:(NSNotification *)note;
  56. - (void)interruptionOccurred:(NSNotification *)notification;
  57. - (void)play;
  58. - (void)playFromURL:(NSURL*)url;
  59. - (void)stop;
  60. - (BOOL)isPlaying;
  61. - (void)pause;
  62. - (void)seekToTime:(unsigned)newSeekTime;
  63. - (unsigned)timePlayedInSeconds;
  64. - (unsigned)durationInSeconds;
  65. @end
  66. @implementation FSAudioStreamPrivate
  67. @synthesize wasInterrupted=_wasInterrupted;
  68. @synthesize onCompletion;
  69. @synthesize onFailure;
  70. -(id)init {
  71. if (self = [super init]) {
  72. _url = nil;
  73. _wasInterrupted = NO;
  74. _wasDisconnected = NO;
  75. _observer = new AudioStreamStateObserver();
  76. _observer->priv = self;
  77. _audioStream = new astreamer::Audio_Stream();
  78. _observer->source = _audioStream;
  79. _audioStream->m_delegate = _observer;
  80. _reachability = [Reachability reachabilityForInternetConnection];
  81. [[NSNotificationCenter defaultCenter] addObserver:self
  82. selector:@selector(reachabilityChanged:)
  83. name:kReachabilityChangedNotification
  84. object:nil];
  85. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  86. [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
  87. #endif
  88. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
  89. [[NSNotificationCenter defaultCenter] addObserver:self
  90. selector:@selector(interruptionOccurred:)
  91. name:AVAudioSessionInterruptionNotification
  92. object:nil];
  93. #endif
  94. }
  95. return self;
  96. }
  97. - (void)dealloc {
  98. [_reachability stopNotifier];
  99. [[NSNotificationCenter defaultCenter] removeObserver:self];
  100. _audioStream->close();
  101. delete _audioStream, _audioStream = nil;
  102. delete _observer, _observer = nil;
  103. }
  104. - (void)setUrl:(NSURL *)url {
  105. if ([self isPlaying]) {
  106. [self stop];
  107. }
  108. @synchronized (self) {
  109. if ([url isEqual:_url]) {
  110. return;
  111. }
  112. _url = [url copy];
  113. _audioStream->setUrl((__bridge CFURLRef)_url);
  114. }
  115. if ([self isPlaying]) {
  116. [self play];
  117. }
  118. }
  119. - (NSURL*)url {
  120. if (!_url) {
  121. return nil;
  122. }
  123. NSURL *copyOfURL = [_url copy];
  124. return copyOfURL;
  125. }
  126. - (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking {
  127. if (_strictContentTypeChecking == strictContentTypeChecking) {
  128. // No change
  129. return;
  130. }
  131. _strictContentTypeChecking = strictContentTypeChecking;
  132. _audioStream->setStrictContentTypeChecking(strictContentTypeChecking);
  133. }
  134. - (BOOL)strictContentTypeChecking {
  135. return _strictContentTypeChecking;
  136. }
  137. - (void)playFromURL:(NSURL*)url {
  138. [self setUrl:url];
  139. [self play];
  140. }
  141. - (void)setDefaultContentType:(NSString *)defaultContentType {
  142. _defaultContentType = [defaultContentType copy];
  143. std::string contentType([_defaultContentType UTF8String]);
  144. _audioStream->setDefaultContentType(contentType);
  145. }
  146. - (NSString*)defaultContentType {
  147. if (!_defaultContentType) {
  148. return nil;
  149. }
  150. NSString *copyOfDefaultContentType = [_defaultContentType copy];
  151. return copyOfDefaultContentType;
  152. }
  153. - (void)reachabilityChanged:(NSNotification *)note {
  154. Reachability *reach = [note object];
  155. NetworkStatus netStatus = [reach currentReachabilityStatus];
  156. BOOL internetConnectionAvailable = (netStatus == ReachableViaWiFi || netStatus == ReachableViaWWAN);
  157. if ([self isPlaying] && !internetConnectionAvailable) {
  158. _wasDisconnected = YES;
  159. }
  160. if (_wasDisconnected && internetConnectionAvailable) {
  161. _wasDisconnected = NO;
  162. /*
  163. * If we call play immediately after the reachability notification,
  164. * the network still fails. Give some time for the network to be actually
  165. * connected.
  166. */
  167. [NSTimer scheduledTimerWithTimeInterval:1
  168. target:self
  169. selector:@selector(play)
  170. userInfo:nil
  171. repeats:NO];
  172. }
  173. }
  174. - (void)interruptionOccurred:(NSNotification *)notification
  175. {
  176. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000)
  177. NSNumber *interruptionType = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey];
  178. if ([interruptionType intValue] == AVAudioSessionInterruptionTypeBegan) {
  179. if ([self isPlaying]) {
  180. self.wasInterrupted = YES;
  181. [self pause];
  182. }
  183. } else if ([interruptionType intValue] == AVAudioSessionInterruptionTypeEnded) {
  184. if (self.wasInterrupted) {
  185. self.wasInterrupted = NO;
  186. [[AVAudioSession sharedInstance] setActive:YES error:nil];
  187. /*
  188. * Resume playing.
  189. */
  190. [self pause];
  191. }
  192. }
  193. #endif
  194. }
  195. - (void)play
  196. {
  197. _audioStream->open();
  198. [_reachability startNotifier];
  199. }
  200. - (void)stop {
  201. _audioStream->close();
  202. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  203. if (_backgroundTask != UIBackgroundTaskInvalid) {
  204. [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
  205. _backgroundTask = UIBackgroundTaskInvalid;
  206. }
  207. #endif
  208. [_reachability stopNotifier];
  209. }
  210. - (BOOL)isPlaying {
  211. return (_audioStream->state() == astreamer::Audio_Stream::PLAYING);
  212. }
  213. - (void)pause {
  214. _audioStream->pause();
  215. }
  216. - (void)seekToTime:(unsigned)newSeekTime {
  217. _audioStream->seekToTime(newSeekTime);
  218. }
  219. - (unsigned)timePlayedInSeconds {
  220. return _audioStream->timePlayedInSeconds();
  221. }
  222. - (unsigned)durationInSeconds {
  223. return _audioStream->durationInSeconds();
  224. }
  225. @end
  226. /*
  227. * ===============================================================
  228. * FSAudioStream public implementation, merely wraps the
  229. * private class.
  230. * ===============================================================
  231. */
  232. @implementation FSAudioStream
  233. -(id)init {
  234. if (self = [super init]) {
  235. _private = [[FSAudioStreamPrivate alloc] init];
  236. }
  237. return self;
  238. }
  239. - (id)initWithUrl:(NSURL *)url
  240. {
  241. if (self = [self init]) {
  242. _private.url = url;
  243. }
  244. return self;
  245. }
  246. - (void)setUrl:(NSURL *)url {
  247. [_private setUrl:url];
  248. }
  249. - (NSURL*)url {
  250. return [_private url];
  251. }
  252. - (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking {
  253. [_private setStrictContentTypeChecking:strictContentTypeChecking];
  254. }
  255. - (BOOL)strictContentTypeChecking {
  256. return [_private strictContentTypeChecking];
  257. }
  258. - (void)setDefaultContentType:(NSString *)defaultContentType {
  259. [_private setDefaultContentType:defaultContentType];
  260. }
  261. - (NSString*)defaultContentType {
  262. return [_private defaultContentType];
  263. }
  264. - (void)play {
  265. [_private play];
  266. }
  267. - (void)playFromURL:(NSURL*)url {
  268. [_private playFromURL:url];
  269. }
  270. - (void)stop {
  271. [_private stop];
  272. }
  273. - (void)pause {
  274. [_private pause];
  275. }
  276. - (void)seekToPosition:(FSStreamPosition)position {
  277. unsigned seekTime = position.minute * 60 + position.second;
  278. [_private seekToTime:seekTime];
  279. }
  280. - (BOOL)isPlaying
  281. {
  282. return [_private isPlaying];
  283. }
  284. - (FSStreamPosition)currentTimePlayed {
  285. unsigned u = [_private timePlayedInSeconds];
  286. unsigned s,m;
  287. s = u % 60, u /= 60;
  288. m = u;
  289. FSStreamPosition pos = {.minute = m, .second = s};
  290. return pos;
  291. }
  292. - (FSStreamPosition)duration {
  293. unsigned u = [_private durationInSeconds];
  294. unsigned s,m;
  295. s = u % 60, u /= 60;
  296. m = u;
  297. FSStreamPosition pos = {.minute = m, .second = s};
  298. return pos;
  299. }
  300. - (BOOL)continuous {
  301. FSStreamPosition duration = self.duration;
  302. return (duration.minute == 0 && duration.second == 0);
  303. }
  304. - (void (^)())onCompletion {
  305. return _private.onCompletion;
  306. }
  307. - (void)setOnCompletion:(void (^)())onCompletion {
  308. _private.onCompletion = onCompletion;
  309. }
  310. - (void (^)())onFailure {
  311. return _private.onFailure;
  312. }
  313. - (void)setOnFailure:(void (^)())onFailure {
  314. _private.onFailure = onFailure;
  315. }
  316. @end
  317. /*
  318. * ===============================================================
  319. * AudioStreamStateObserver: listen to the state from the audio stream.
  320. * ===============================================================
  321. */
  322. void AudioStreamStateObserver::audioStreamErrorOccurred(int errorCode)
  323. {
  324. NSDictionary *userInfo = @{FSAudioStreamNotificationKey_Error: @(errorCode),
  325. FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
  326. NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamErrorNotification object:nil userInfo:userInfo];
  327. [[NSNotificationCenter defaultCenter] postNotification:notification];
  328. }
  329. void AudioStreamStateObserver::audioStreamStateChanged(astreamer::Audio_Stream::State state)
  330. {
  331. NSNumber *fsAudioState;
  332. switch (state) {
  333. case astreamer::Audio_Stream::STOPPED:
  334. fsAudioState = [NSNumber numberWithInt:kFsAudioStreamStopped];
  335. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  336. [[AVAudioSession sharedInstance] setActive:NO error:nil];
  337. #endif
  338. if (m_eofReached && priv.onCompletion) {
  339. priv.onCompletion();
  340. }
  341. break;
  342. case astreamer::Audio_Stream::BUFFERING:
  343. m_eofReached = false;
  344. fsAudioState = [NSNumber numberWithInt:kFsAudioStreamBuffering];
  345. break;
  346. case astreamer::Audio_Stream::PLAYING:
  347. m_eofReached = false;
  348. fsAudioState = [NSNumber numberWithInt:kFsAudioStreamPlaying];
  349. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  350. [[AVAudioSession sharedInstance] setActive:YES error:nil];
  351. #endif
  352. break;
  353. case astreamer::Audio_Stream::SEEKING:
  354. m_eofReached = false;
  355. fsAudioState = [NSNumber numberWithInt:kFsAudioStreamSeeking];
  356. break;
  357. case astreamer::Audio_Stream::END_OF_FILE:
  358. m_eofReached = true;
  359. fsAudioState = [NSNumber numberWithInt:kFSAudioStreamEndOfFile];
  360. break;
  361. case astreamer::Audio_Stream::FAILED:
  362. m_eofReached = false;
  363. fsAudioState = [NSNumber numberWithInt:kFsAudioStreamFailed];
  364. #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000)
  365. [[AVAudioSession sharedInstance] setActive:NO error:nil];
  366. #endif
  367. if (priv.onFailure) {
  368. priv.onFailure();
  369. }
  370. break;
  371. default:
  372. /* unknown state */
  373. return;
  374. break;
  375. }
  376. NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: fsAudioState,
  377. FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
  378. NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:nil userInfo:userInfo];
  379. [[NSNotificationCenter defaultCenter] postNotification:notification];
  380. }
  381. void AudioStreamStateObserver::audioStreamMetaDataAvailable(std::map<CFStringRef,CFStringRef> metaData)
  382. {
  383. NSMutableDictionary *metaDataDictionary = [[NSMutableDictionary alloc] init];
  384. for (std::map<CFStringRef,CFStringRef>::iterator iter = metaData.begin(); iter != metaData.end(); ++iter) {
  385. CFStringRef key = iter->first;
  386. CFStringRef value = iter->second;
  387. metaDataDictionary[CFBridgingRelease(key)] = CFBridgingRelease(value);
  388. }
  389. NSDictionary *userInfo = @{FSAudioStreamNotificationKey_MetaData: metaDataDictionary,
  390. FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]};
  391. NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamMetaDataNotification object:nil userInfo:userInfo];
  392. [[NSNotificationCenter defaultCenter] postNotification:notification];
  393. }