Merge lp:~rockstar/ubuntuone-ios-music/remove-audiostreamer into lp:ubuntuone-ios-music
- remove-audiostreamer
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Review via email:
|
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 */, |