Merge lp:~rockstar/ubuntuone-ios-music/remove-audiostreamer into lp:ubuntuone-ios-music

Proposed by Paul Hummer
Status: Merged
Approved by: Paul Hummer
Approved revision: 260
Merged at revision: 252
Proposed branch: lp:~rockstar/ubuntuone-ios-music/remove-audiostreamer
Merge into: lp:ubuntuone-ios-music
Diff against target: 428 lines (+98/-96)
8 files modified
Music/Models/Song.h (+1/-0)
Music/Models/Song.m (+6/-0)
Music/Utilities/UOPlayer.h (+5/-4)
Music/Utilities/UOPlayer.m (+73/-85)
Music/View Controllers/AlbumViewController.m (+1/-2)
Music/View Controllers/PlayerViewController.m (+3/-3)
Music/View Controllers/SongsViewController.m (+1/-2)
U1Music.xcodeproj/project.pbxproj (+8/-0)
To merge this branch: bzr merge lp:~rockstar/ubuntuone-ios-music/remove-audiostreamer
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+148100@code.launchpad.net

Commit message

Replace AudioStreamer dependency with AVPlayer

Description of the change

This branch removes the need for AudioStreamer, a dilapidated dependency that hasn't really been needed since iOS 4, but has remained around for historical reasons. I've swapped it out for iOS's AVPlayer.

This has a wonderful side effect of removing a few (potential) memory leaks. UOPlayer hasn't lost any functionality in this change, and will make it easier to implement scrubbing when we get around to it.

To post a comment you must log in.
259. By Paul Hummer

Add support for the AudioSession (for backgrounding)

260. By Paul Hummer

Use notifications rather than the fragile time observer

Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Music/Models/Song.h'
2--- Music/Models/Song.h 2013-02-07 00:26:32 +0000
3+++ Music/Models/Song.h 2013-02-13 18:19:23 +0000
4@@ -44,5 +44,6 @@
5
6 @property (nonatomic, retain, readonly) NSString *cacheFilePath;
7 @property (nonatomic, readonly) BOOL isDownloaded;
8+@property (nonatomic, readonly) NSURL *localURL;
9
10 @end
11
12=== modified file 'Music/Models/Song.m'
13--- Music/Models/Song.m 2013-01-28 23:11:50 +0000
14+++ Music/Models/Song.m 2013-02-13 18:19:23 +0000
15@@ -179,6 +179,12 @@
16 return [[NSFileManager defaultManager] fileExistsAtPath:self.cacheFilePath];
17 }
18
19+- (NSURL *)localURL {
20+ NSString *songPath = [[self idPath] urlParameterEncodedString];
21+ NSURL *localURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:9990/%@", songPath]];
22+ return localURL;
23+}
24+
25 /* This method exists for historical reasons currently, but should be deprecated soon. It formulates a path to send to the
26 local auth server, and that auth server then disassembles the path to get the needed info out of it. I think it'd be much
27 more efficient to just pass the song id currently.
28
29=== modified file 'Music/Utilities/UOPlayer.h'
30--- Music/Utilities/UOPlayer.h 2013-02-09 05:59:29 +0000
31+++ Music/Utilities/UOPlayer.h 2013-02-13 18:19:23 +0000
32@@ -7,6 +7,7 @@
33 //
34
35 #import <Foundation/Foundation.h>
36+#import <CoreMedia/CoreMedia.h>
37
38 @class Song;
39
40@@ -30,8 +31,8 @@
41 @interface UOPlayer : NSObject
42 + (id)player;
43
44-- (void)addSongs:(NSArray *)songs;
45-- (void)addSongs:(NSArray *)songs withIndex:(NSUInteger)index;
46+- (void)setSongs:(NSArray *)songs;
47+- (void)setSongs:(NSArray *)songs withIndex:(NSUInteger)index;
48
49 - (void)playPause;
50 - (void)next;
51@@ -40,11 +41,11 @@
52 @property (nonatomic, readonly) NSUInteger currentIndex;
53 @property (nonatomic, readonly) Song *currentSong;
54 @property (nonatomic, readonly) NSArray *currentPlaylist;
55-@property PlayerState state;
56+@property (nonatomic, readonly) PlayerState state;
57
58 @property (nonatomic) BOOL shuffle;
59 @property (nonatomic) RepeatState repeat;
60
61-- (double)streamerProgress;
62+- (CMTime)streamerProgress;
63
64 @end
65
66=== modified file 'Music/Utilities/UOPlayer.m'
67--- Music/Utilities/UOPlayer.m 2013-02-09 05:59:29 +0000
68+++ Music/Utilities/UOPlayer.m 2013-02-13 18:19:23 +0000
69@@ -7,17 +7,26 @@
70 //
71
72 #import "UOPlayer.h"
73-#import "AudioStreamer.h"
74 #import "Song.h"
75+#import "Artist.h"
76+#import "Album.h"
77+#import <AVFoundation/AVFoundation.h>
78+#import <CoreMedia/CoreMedia.h>
79+#import <MediaPlayer/MediaPlayer.h>
80
81 #import "U1AutoDownloadsManager.h"
82
83-@interface UOPlayer () {
84+#define AVPLAYERRATEPAUSE 0.0
85+#define AVPLAYERRATEPLAY 1.0
86+#define AVPLAYERPREVTHRESHOLD 5.0
87+
88+@interface UOPlayer () <AVAudioSessionDelegate> {
89 NSArray *songs;
90 NSArray *shuffledSongs;
91
92- AudioStreamer *streamer;
93 float seekTime;
94+
95+ AVPlayer *player;
96 }
97 @property (nonatomic, readwrite) NSUInteger currentIndex;
98 @property (nonatomic, strong) U1AutoDownloadsManager *downloadManager;
99@@ -47,22 +56,31 @@
100 if (self == nil) { return self; }
101
102 [self restoreSettings];
103-
104 self.downloadManager = [[U1AutoDownloadsManager alloc] init];
105-
106- self.state = PLAYERSTATESTOP;
107- [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
108-
109 [self addObserver:self forKeyPath:@"currentIndex" options:nil context:NULL];
110
111+ player = [[AVPlayer alloc] init];
112+ NSError *error;
113+ AVAudioSession *audioSession = [AVAudioSession sharedInstance];
114+ [audioSession setDelegate:self];
115+ if ([audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) {
116+ if ([audioSession setActive:YES error:nil]) {
117+ [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
118+ } else {
119+ NSLog(@"AVAudioSession not active. Reason: %@", error.localizedDescription);
120+ }
121+ } else {
122+ NSLog(@"AVAudioSession category not set. Reason: %@", error.localizedDescription);
123+ }
124+
125+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(songComplete) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
126+
127+ [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
128 return self;
129 }
130
131 - (void)dealloc {
132- if (streamer) {
133- [streamer stop];
134- streamer = nil;
135- }
136+ player = nil;
137 songs = nil;
138 shuffledSongs = nil;
139 downloadManager = nil;
140@@ -72,7 +90,7 @@
141
142 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
143 if ([keyPath isEqual:@"currentIndex"]) {
144- [self.downloadManager setPlaylist:[self currentPlaylist] andIndex:self.currentIndex];
145+ [self currentIndexChange];
146 } else {
147 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
148 }
149@@ -106,8 +124,8 @@
150
151 #pragma mark - Player state
152
153-- (double)streamerProgress {
154- return streamer.progress;
155+- (CMTime)streamerProgress {
156+ return player.currentTime;
157 }
158
159 #pragma mark - Play queue management
160@@ -123,17 +141,12 @@
161 return (shuffle ? shuffledSongs : songs);
162 }
163
164-- (void)addSongs:(NSArray *)_songs {
165- [self addSongs:_songs withIndex:0];
166+- (void)setSongs:(NSArray *)_songs {
167+ [self setSongs:_songs withIndex:0];
168 }
169
170-- (void)addSongs:(NSArray *)_songs withIndex:(NSUInteger)index {
171- if (streamer != nil) {
172- [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
173- self.state = PLAYERSTATESTOP;
174- [streamer stop];
175- streamer = nil;
176- }
177+- (void)setSongs:(NSArray *)_songs withIndex:(NSUInteger)index {
178+ [player pause];
179
180 songs = _songs;
181
182@@ -151,21 +164,15 @@
183 } else {
184 self.currentIndex = index;
185 }
186-
187-
188 }
189
190 #pragma mark - Player controls
191
192 - (void)playPause {
193- if ([streamer isPaused]) {
194- self.state = PLAYERSTATEPLAY;
195- [streamer start];
196- } else if ([streamer isPlaying]) {
197- self.state = PLAYERSTATEPAUSE;
198- [streamer pause];
199- } else {
200- [self _playCurrentSong];
201+ if (player.rate == AVPLAYERRATEPAUSE) {
202+ [player play];
203+ } else if (player.rate == AVPLAYERRATEPLAY) {
204+ [player pause];
205 }
206 [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
207 }
208@@ -173,63 +180,45 @@
209 - (void)next {
210 switch (repeat) {
211 case REPEATOFF:
212- [streamer stop];
213- self.state = PLAYERSTATESTOP;
214- if (self.currentIndex == ([[self currentPlaylist] count] - 1)) {
215- [streamer stop];
216- streamer = nil;
217+ case REPEATONE:
218+ if (self.currentIndex < ([[self currentPlaylist] count] - 1)) {
219+ self.currentIndex = self.currentIndex + 1;
220 } else {
221- self.currentIndex = self.currentIndex + 1;
222- [self playPause];
223+ return;
224 }
225 break;
226- case REPEATONE:
227- [streamer stop];
228- [self playPause];
229- break;
230 case REPEATALL:
231 if (self.currentIndex == [[self currentPlaylist] count] - 1) {
232 self.currentIndex = 0;
233 } else {
234 self.currentIndex = self.currentIndex + 1;
235 }
236- self.state = PLAYERSTATESTOP;
237- [streamer stop];
238- [self playPause];
239 break;
240 }
241- [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
242 }
243
244 - (void)prev {
245+ if (CMTimeGetSeconds(player.currentTime) > AVPLAYERPREVTHRESHOLD) {
246+ /* If we're into the song already, just touch the currentIndex to restart it. */
247+ self.currentIndex = self.currentIndex;
248+ return;
249+ }
250+
251 switch (repeat) {
252 case REPEATOFF:
253- [streamer stop];
254- self.state = PLAYERSTATESTOP;
255- if (self.currentIndex == 0) {
256- [streamer stop];
257- streamer = nil;
258- } else {
259+ case REPEATONE:
260+ if (self.currentIndex != 0) {
261 self.currentIndex = self.currentIndex - 1;
262- [self playPause];
263 }
264 break;
265- case REPEATONE:
266- [streamer stop];
267- [self playPause];
268- break;
269 case REPEATALL:
270 if (self.currentIndex == 0) {
271 self.currentIndex = [[self currentPlaylist] count] - 1;
272 } else {
273 self.currentIndex = self.currentIndex - 1;
274 }
275- self.state = PLAYERSTATESTOP;
276- [streamer stop];
277- [self playPause];
278 break;
279 }
280- [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
281 }
282
283 #pragma mark - Setters
284@@ -255,32 +244,31 @@
285
286 #pragma mark - Private methods
287
288-- (void)_playCurrentSong {
289- if (streamer != nil) {
290- [streamer stop];
291- streamer = nil;
292- }
293-
294- seekTime = 0.0;
295-
296- Song *currentSong = [[self currentPlaylist] objectAtIndex:self.currentIndex];
297-
298- NSString *songPath = [[currentSong idPath] urlParameterEncodedString];
299- NSURL *localURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:9990/%@", songPath]];
300- streamer = [[AudioStreamer alloc] initWithURL:localURL];
301- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(streamerStateChanged:) name:ASStatusChangedNotification object:streamer];
302-
303- [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
304- self.state = PLAYERSTATEPLAY;
305- [streamer start];
306+- (void)currentIndexChange {
307+ [self.downloadManager setPlaylist:[self currentPlaylist] andIndex:self.currentIndex];
308+
309+ Song *song = [[self currentPlaylist] objectAtIndex:self.currentIndex];
310+ AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:song.localURL];
311+ [player replaceCurrentItemWithPlayerItem:item];
312+ [player play];
313
314 [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerSongChanged object:nil];
315+ [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerStateChanged object:nil];
316 }
317
318-- (void)streamerStateChanged:(NSNotification *)notification {
319- if ([streamer isIdle]) {
320- [self next];
321+- (PlayerState)state {
322+ if (player.currentItem == nil) {
323+ return PLAYERSTATESTOP;
324+ } else if (player.rate == AVPLAYERRATEPLAY) {
325+ return PLAYERSTATEPLAY;
326+ } else if (player.rate == AVPLAYERRATEPAUSE) {
327+ return PLAYERSTATEPAUSE;
328 }
329+ return PLAYERSTATESTOP;
330+}
331+
332+- (void)songComplete {
333+ [self next];
334 }
335
336 @end
337
338=== modified file 'Music/View Controllers/AlbumViewController.m'
339--- Music/View Controllers/AlbumViewController.m 2013-02-11 03:55:13 +0000
340+++ Music/View Controllers/AlbumViewController.m 2013-02-13 18:19:23 +0000
341@@ -158,8 +158,7 @@
342 NSUInteger index = [songs indexOfObject:song];
343
344 UOPlayer *player = [UOPlayer player];
345- [player addSongs:songs withIndex:index];
346- [player playPause];
347+ [player setSongs:songs withIndex:index];
348 }
349
350 @end
351
352=== modified file 'Music/View Controllers/PlayerViewController.m'
353--- Music/View Controllers/PlayerViewController.m 2013-02-11 04:18:27 +0000
354+++ Music/View Controllers/PlayerViewController.m 2013-02-13 18:19:23 +0000
355@@ -188,9 +188,9 @@
356 }
357
358 - (void)heartbeat:(NSTimer*)timer {
359- double progress = [[UOPlayer player] streamerProgress];
360- self.elapsedTime.text = [NSString clockStringFromSeconds:(int)progress];
361- self.progressView.value = progress;
362+ CMTime time = [[UOPlayer player] streamerProgress];
363+ self.elapsedTime.text = [NSString clockStringFromSeconds:CMTimeGetSeconds(time)];
364+ self.progressView.value = (double)CMTimeGetSeconds(time);
365 }
366
367 @end
368
369=== modified file 'Music/View Controllers/SongsViewController.m'
370--- Music/View Controllers/SongsViewController.m 2013-02-11 03:55:13 +0000
371+++ Music/View Controllers/SongsViewController.m 2013-02-13 18:19:23 +0000
372@@ -73,8 +73,7 @@
373 NSUInteger index = [songs indexOfObject:song];
374
375 UOPlayer *player = [UOPlayer player];
376- [player addSongs:songs withIndex:index];
377- [player playPause];
378+ [player setSongs:songs withIndex:index];
379 }
380
381 @end
382
383=== modified file 'U1Music.xcodeproj/project.pbxproj'
384--- U1Music.xcodeproj/project.pbxproj 2013-02-11 04:11:56 +0000
385+++ U1Music.xcodeproj/project.pbxproj 2013-02-13 18:19:23 +0000
386@@ -98,6 +98,8 @@
387 52AC3D811604513E00B4785D /* uncached.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D651604513E00B4785D /* uncached.png */; };
388 52AC3D821604513E00B4785D /* uncached@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D661604513E00B4785D /* uncached@2x.png */; };
389 52AC3D841604539000B4785D /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D831604539000B4785D /* Default-568h@2x.png */; };
390+ 52BC20D516CB18A100E22294 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52BC20D416CB18A100E22294 /* AVFoundation.framework */; };
391+ 52BC20DC16CB417700E22294 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52BC20DB16CB417700E22294 /* CoreMedia.framework */; };
392 52E7F4A316AF1522003A46DA /* UbuntuOneAuthKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528515941604F16D004A1F7C /* UbuntuOneAuthKit.a */; };
393 536D620B1144495400DFCE56 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 536D620A1144495400DFCE56 /* SystemConfiguration.framework */; };
394 537DE2D9113F008C00875852 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 537DE2D8113F008C00875852 /* CoreFoundation.framework */; };
395@@ -380,6 +382,8 @@
396 52AC3D651604513E00B4785D /* uncached.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uncached.png; sourceTree = "<group>"; };
397 52AC3D661604513E00B4785D /* uncached@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "uncached@2x.png"; sourceTree = "<group>"; };
398 52AC3D831604539000B4785D /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
399+ 52BC20D416CB18A100E22294 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
400+ 52BC20DB16CB417700E22294 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
401 536D620A1144495400DFCE56 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
402 537DE2D8113F008C00875852 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
403 53F675D7113B092C00822059 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
404@@ -626,6 +630,8 @@
405 isa = PBXFrameworksBuildPhase;
406 buildActionMask = 2147483647;
407 files = (
408+ 52BC20DC16CB417700E22294 /* CoreMedia.framework in Frameworks */,
409+ 52BC20D516CB18A100E22294 /* AVFoundation.framework in Frameworks */,
410 53F675E8113B096400822059 /* AudioToolbox.framework in Frameworks */,
411 53F675DC113B093900822059 /* CFNetwork.framework in Frameworks */,
412 93DFFE3F135D70B60061F29F /* CoreData.framework in Frameworks */,
413@@ -661,6 +667,7 @@
414 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
415 isa = PBXGroup;
416 children = (
417+ 52BC20DB16CB417700E22294 /* CoreMedia.framework */,
418 5268508516AE516B001F65A6 /* RestKit.xcodeproj */,
419 5285158E1604F16B004A1F7C /* UbuntuOneAuthKit.xcodeproj */,
420 93F3346C1247FB78006C6707 /* Music */,
421@@ -693,6 +700,7 @@
422 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
423 isa = PBXGroup;
424 children = (
425+ 52BC20D416CB18A100E22294 /* AVFoundation.framework */,
426 5279764815F00B2600F8435F /* libz.dylib */,
427 5257416C16C5653100530CCC /* Crashlytics.framework */,
428 5268510716AEFD20001F65A6 /* MobileCoreServices.framework */,

Subscribers

People subscribed via source and target branches