Skip to content

Commit

Permalink
Implemented MPMusicPlayerController
Browse files Browse the repository at this point in the history
  • Loading branch information
indragiek committed Aug 29, 2012
1 parent d5e4f52 commit 9336648
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 44 deletions.
36 changes: 36 additions & 0 deletions Audio Players/SMKMPMusicPlayer.h
@@ -0,0 +1,36 @@
//
// SMKMPMusicPlayer.h
// SNRMusicKit
//
// Created by Indragie Karunaratne on 2012-08-28.
// Copyright (c) 2012 Indragie Karunaratne. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "SMKPlayer.h"

/**
SMKMPMusicPlayer is a wrapper for MPMusicPlayerController. It's implemented because
AVQueuePlayer doesn't support iTunes Match streaming/downloading.
SMKMPMediaTrack will automatically use this player when it's a track on the cloud
*/
@interface SMKMPMusicPlayer : NSObject <SMKPlayer>

@property (nonatomic, copy) void (^finishedTrackBlock)(id<SMKPlayer> player, id<SMKTrack> track, NSError *error);
@property (nonatomic, strong, readonly) MPMusicPlayerController *audioPlayer;

@property (nonatomic, assign) NSTimeInterval seekTimeInterval;
@property (nonatomic, assign, readonly) NSTimeInterval playbackTime;
@property (nonatomic, assign, readonly) BOOL playing;
@property (nonatomic, strong, readonly) id<SMKTrack> currentTrack;

- (void)pause;
- (void)play;

// Seeking (SMKPlayer @optional)
- (void)seekToPlaybackTime:(NSTimeInterval)time;
- (void)seekBackward;
- (void)seekForward;
@end
124 changes: 124 additions & 0 deletions Audio Players/SMKMPMusicPlayer.m
@@ -0,0 +1,124 @@
//
// SMKMPMusicPlayer.m
// SNRMusicKit
//
// Created by Indragie Karunaratne on 2012-08-28.
// Copyright (c) 2012 Indragie Karunaratne. All rights reserved.
//

#import "SMKMPMusicPlayer.h"
#import "SMKMPMediaTrack.h"
#import "NSError+SMKAdditions.h"

@interface SMKMPMusicPlayer ()
@property (nonatomic, strong, readwrite) MPMusicPlayerController *audioPlayer;
@property (nonatomic, assign, readwrite) BOOL playing;
@property (nonatomic, strong) id<SMKTrack> oldCurrentTrack;
@end

@implementation SMKMPMusicPlayer
- (id)init
{
if ((self = [super init])) {
self.audioPlayer = [MPMusicPlayerController applicationMusicPlayer];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(_playbackStateDidChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.audioPlayer];
[nc addObserver:self selector:@selector(_nowPlayingItemDidChange:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.audioPlayer];
[self.audioPlayer beginGeneratingPlaybackNotifications];
self.audioPlayer.repeatMode = MPMusicRepeatModeNone;
self.audioPlayer.shuffleMode = MPMusicShuffleModeOff;
}
return self;
}

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - SMKPlayer

+ (NSSet *)supportedContentSourceClasses
{
return [NSSet setWithObject:NSClassFromString(@"SMKMPMediaContentSource")];
}

+ (BOOL)supportsPreloading
{
return NO;
}

- (BOOL)supportsSeeking
{
return YES;
}

- (id<SMKTrack>)currentTrack
{
return self.oldCurrentTrack;
}

- (void)playTrack:(id<SMKTrack>)track completionHandler:(void(^)(NSError *error))handler
{
if ([track isKindOfClass:[SMKMPMediaTrack class]]) {
SMKMPMediaTrack *mediaTrack = (SMKMPMediaTrack *)track;
MPMediaItem *mediaItem = mediaTrack.representedObject;
MPMediaItemCollection *collection = [MPMediaItemCollection collectionWithItems:@[mediaItem]];
[self.audioPlayer setQueueWithItemCollection:collection];
[self.audioPlayer play];
if (handler) { handler(nil); }
} else if (handler) {
NSError *error = [NSError SMK_errorWithCode:SMKPlayerErrorFailedToEnqueueTrack description:@"MPMusicPlayerController failed to enqueue the track"];
handler(error);
}
}

- (void)pause
{
[self.audioPlayer pause];
}

- (void)play
{
[self.audioPlayer play];
}

- (NSTimeInterval)playbackTime
{
return self.audioPlayer.currentPlaybackTime;
}

+ (NSSet *)keyPathsForValuesAffectingPlaybackTime
{
return [NSSet setWithObject:@"audioPlayer.currentPlaybackTime"];
}

- (void)seekToPlaybackTime:(NSTimeInterval)time
{
self.audioPlayer.currentPlaybackTime = time;
}

- (void)seekBackward
{
self.audioPlayer.currentPlaybackTime = self.audioPlayer.currentPlaybackTime - self.seekTimeInterval;
}

- (void)seekForward
{
self.audioPlayer.currentPlaybackTime = self.audioPlayer.currentPlaybackTime + self.seekTimeInterval;
}

#pragma mark - Notifications

- (void)_playbackStateDidChange:(NSNotification *)notification
{
self.playing = self.audioPlayer.playbackState == MPMusicPlaybackStatePlaying;
}

- (void)_nowPlayingItemDidChange:(NSNotification *)notification
{
if (!self.audioPlayer.nowPlayingItem && self.finishedTrackBlock) {
self.finishedTrackBlock(self, self.oldCurrentTrack, nil);
}
}
@end
2 changes: 1 addition & 1 deletion Audio Players/SMKSpotifyPlayer.h
Expand Up @@ -24,9 +24,9 @@
@property (nonatomic, assign, readonly) NSTimeInterval playbackTime; @property (nonatomic, assign, readonly) NSTimeInterval playbackTime;
@property (nonatomic, assign, readonly) BOOL playing; @property (nonatomic, assign, readonly) BOOL playing;
@property (nonatomic, assign) float volume; @property (nonatomic, assign) float volume;
@property (nonatomic, strong, readonly) id<SMKTrack> currentTrack;


- (id)initWithPlaybackSession:(SPSession *)aSession; - (id)initWithPlaybackSession:(SPSession *)aSession;
- (id<SMKTrack>)currentTrack;


// Seeking (SMKPlayer @optional) // Seeking (SMKPlayer @optional)
- (void)seekToPlaybackTime:(NSTimeInterval)time; - (void)seekToPlaybackTime:(NSTimeInterval)time;
Expand Down
35 changes: 17 additions & 18 deletions Audio Players/SMKSpotifyPlayer.m
Expand Up @@ -23,38 +23,22 @@ - (id)initWithPlaybackSession:(SPSession *)aSession
if ((self = [super init])) { if ((self = [super init])) {
self.audioPlayer = [[SPPlaybackManager alloc] initWithPlaybackSession:aSession]; self.audioPlayer = [[SPPlaybackManager alloc] initWithPlaybackSession:aSession];
self.seekTimeInterval = SMKPlayerDefaultSeekTimeInterval; self.seekTimeInterval = SMKPlayerDefaultSeekTimeInterval;
[self.audioPlayer addObserver:self forKeyPath:@"playbackSession.playing" options:NSKeyValueObservingOptionNew context:NULL];
[self.audioPlayer addObserver:self forKeyPath:@"trackPosition" options:NSKeyValueObservingOptionNew context:NULL];
[self.audioPlayer addObserver:self forKeyPath:@"currentTrack" options:NSKeyValueObservingOptionNew context:NULL]; [self.audioPlayer addObserver:self forKeyPath:@"currentTrack" options:NSKeyValueObservingOptionNew context:NULL];
} }
return self; return self;
} }


- (void)dealloc - (void)dealloc
{ {
[_audioPlayer removeObserver:self forKeyPath:@"playbackSession.playing"];
[_audioPlayer removeObserver:self forKeyPath:@"trackPosition"];
[_audioPlayer removeObserver:self forKeyPath:@"currentTrack"]; [_audioPlayer removeObserver:self forKeyPath:@"currentTrack"];
} }


#pragma mark - KVO #pragma mark - KVO


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{ {
if (object != self.audioPlayer) { return; } if ([keyPath isEqualToString:@"currentTrack"] && object == self.audioPlayer) {
id newValue = [change valueForKey:NSKeyValueChangeNewKey]; if (!SMKObjectIsValid([change valueForKey:NSKeyValueChangeNewKey])) {
if ([keyPath isEqualToString:@"playbackSession.playing"]) {
[self willChangeValueForKey:@"playing"];
_playing = [newValue boolValue];
[self didChangeValueForKey:@"playing"];
if (context != NULL)
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
} else if ([keyPath isEqualToString:@"trackPosition"]) {
[self willChangeValueForKey:@"playbackTime"];
_playbackTime = [newValue doubleValue];
[self didChangeValueForKey:@"playbackTime"];
} else if ([keyPath isEqualToString:@"currentTrack"]) {
if (!SMKObjectIsValid(newValue)) {
if (self.finishedTrackBlock) if (self.finishedTrackBlock)
self.finishedTrackBlock(self, self.oldCurrentTrack, nil); self.finishedTrackBlock(self, self.oldCurrentTrack, nil);
self.oldCurrentTrack = nil; self.oldCurrentTrack = nil;
Expand Down Expand Up @@ -158,4 +142,19 @@ + (NSSet *)keyPathsForValuesAffectingVolume
{ {
return [NSSet setWithObject:@"audioPlayer.volume"]; return [NSSet setWithObject:@"audioPlayer.volume"];
} }

+ (NSSet *)keyPathsForValuesAffectingPlaying
{
return [NSSet setWithObject:@"audioPlayer.playbackSession.playing"];
}

+ (NSSet *)keyPathsForValuesAffectingPlaybackTime
{
return [NSSet setWithObject:@"audioPlayer.trackPosition"];
}

+ (NSSet *)keyPathsForValuesAffectingCurrentTrack
{
return [NSSet setWithObject:@"audioPlayer.currentTrack"];
}
@end @end
4 changes: 2 additions & 2 deletions Content Sources/MPMediaLibrary/Data Model/SMKMPMediaTrack.m
Expand Up @@ -42,9 +42,9 @@ + (NSSet *)supportedSortKeys
return [NSSet setWithObjects:@"name", @"artistName", @"albumArtistName", @"duration", @"composer", @"trackNumber", @"discNumber", @"playCount", @"lyrics", @"genre", @"rating", @"lastPlayedDate", nil]; return [NSSet setWithObjects:@"name", @"artistName", @"albumArtistName", @"duration", @"composer", @"trackNumber", @"discNumber", @"playCount", @"lyrics", @"genre", @"rating", @"lastPlayedDate", nil];
} }


+ (Class)playerClass - (Class)playerClass
{ {
return NSClassFromString(@"SMKAVQueuePlayer"); return NSClassFromString((self.playbackURL != nil) ? @"SMKAVQueuePlayer" : @"SMKMPMusicPlayer");
} }


#pragma mark - SMKTrack #pragma mark - SMKTrack
Expand Down
2 changes: 1 addition & 1 deletion Content Sources/Spotify/Data Model/SPTrack+SMKTrack.m
Expand Up @@ -47,7 +47,7 @@ + (NSSet *)supportedSortKeys
return [NSSet setWithObjects:@"artist", @"artistName", @"albumArtistName", @"duration", @"trackNumber", @"discNumber", @"name", @"popularity", nil]; return [NSSet setWithObjects:@"artist", @"artistName", @"albumArtistName", @"duration", @"trackNumber", @"discNumber", @"name", @"popularity", nil];
} }


+ (Class)playerClass - (Class)playerClass
{ {
return NSClassFromString(@"SMKSpotifyPlayer"); return NSClassFromString(@"SMKSpotifyPlayer");
} }
Expand Down
Expand Up @@ -59,7 +59,7 @@ + (NSSet *)supportedSortKeys
return [NSSet setWithObjects:@"album", @"duration", @"composer", @"trackNumber", @"discNumber", @"isExplicit", @"isClean", @"genre", @"rating", nil]; return [NSSet setWithObjects:@"album", @"duration", @"composer", @"trackNumber", @"discNumber", @"isExplicit", @"isClean", @"genre", @"rating", nil];
} }


+ (Class)playerClass - (Class)playerClass
{ {
return NSClassFromString(@"SMKAVQueuePlayer"); return NSClassFromString(@"SMKAVQueuePlayer");
} }
Expand Down
7 changes: 4 additions & 3 deletions Example Apps/MPMediaLibraryExample/DetailViewController.m
Expand Up @@ -9,7 +9,7 @@
#import "DetailViewController.h" #import "DetailViewController.h"
#import "SNRMusicKitiOS.h" #import "SNRMusicKitiOS.h"
#import "SMKMPMediaContentSource.h" #import "SMKMPMediaContentSource.h"
#import "SMKAVQueuePlayer.h" #import "SMKMPMusicPlayer.h"


@interface DetailViewController () @interface DetailViewController ()
@property (strong, nonatomic) UIPopoverController *masterPopoverController; @property (strong, nonatomic) UIPopoverController *masterPopoverController;
Expand All @@ -18,7 +18,7 @@ - (void)configureView;
@end @end


@implementation DetailViewController { @implementation DetailViewController {
SMKAVQueuePlayer *_player; SMKMPMusicPlayer *_player;
} }


#pragma mark - Managing the detail item #pragma mark - Managing the detail item
Expand All @@ -42,7 +42,7 @@ - (void)configureView
strongSelf.tracks = tracks; strongSelf.tracks = tracks;
[self.tableView reloadData]; [self.tableView reloadData];
}]; }];
_player = [SMKAVQueuePlayer new]; _player = [SMKMPMusicPlayer new];
} }


- (void)viewDidLoad - (void)viewDidLoad
Expand Down Expand Up @@ -75,6 +75,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{ {
SMKMPMediaTrack *object = self.tracks[indexPath.row]; SMKMPMediaTrack *object = self.tracks[indexPath.row];
NSLog(@"%@", [object playbackURL]);
[_player playTrack:object completionHandler:^(NSError *error) { [_player playTrack:object completionHandler:^(NSError *error) {
NSLog(@"%@", error); NSLog(@"%@", error);
}]; }];
Expand Down
21 changes: 11 additions & 10 deletions Protocols/SMKPlayer.h
Expand Up @@ -11,6 +11,7 @@


@protocol SMKPlayerDelegate; @protocol SMKPlayerDelegate;
@protocol SMKPlayer <NSObject> @protocol SMKPlayer <NSObject>
@required
/** /**
@return An set of class names containing the names of content sources that this player can play. @return An set of class names containing the names of content sources that this player can play.
*/ */
Expand Down Expand Up @@ -68,20 +69,14 @@
@property (nonatomic, assign) float volume; @property (nonatomic, assign) float volume;


/** /**
Seek to the specified time if the player supports seeking. The seek time interval for the -seekBackward and -seekForward methods.
*/
- (void)seekToPlaybackTime:(NSTimeInterval)time;

/**
Return the seek time interval for the -seekBackward and -seekForward methods.
*/ */
- (NSTimeInterval)seekTimeInterval; @property (nonatomic, assign) NSTimeInterval seekTimeInterval;


/** /**
Set the seek time interval for the -seekBackward and -seekForward methods. Seek to the specified time if the player supports seeking.
@param interval The seek time interval.
*/ */
- (void)setSeekTimeInterval:(NSTimeInterval)interval; - (void)seekToPlaybackTime:(NSTimeInterval)time;


/** /**
Seek backward (-seekTimeInterval) seconds Seek backward (-seekTimeInterval) seconds
Expand Down Expand Up @@ -113,4 +108,10 @@
A block called when the current track finishes playing A block called when the current track finishes playing
*/ */
@property (nonatomic, copy) void (^finishedTrackBlock)(id<SMKPlayer> player, id<SMKTrack> track, NSError *error); @property (nonatomic, copy) void (^finishedTrackBlock)(id<SMKPlayer> player, id<SMKTrack> track, NSError *error);

/**
Returns the underlying player object that does all the real work.
Useful if you want to do customization beyond SMKPlayer's API
*/
- (id)audioPlayer;
@end @end
2 changes: 1 addition & 1 deletion Protocols/SMKTrack.h
Expand Up @@ -43,7 +43,7 @@
/** /**
@return The player class used to play this track @return The player class used to play this track
*/ */
+ (Class)playerClass; - (Class)playerClass;


@optional @optional


Expand Down
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -56,6 +56,9 @@ git submodule update
<tr> <tr>
<td>AVQueuePlayer</td><td>✔</td><td>✔</td><td>✔</td> <td>AVQueuePlayer</td><td>✔</td><td>✔</td><td>✔</td>
</tr> </tr>
<tr>
<td>MPMusicPlayerController</td><td>✔</td><td>✘</td><td>✔</td>
</tr>
<tr> <tr>
<td>SFBAudioEngine</td><td>✔</td><td>✔</td><td>✔</td> <td>SFBAudioEngine</td><td>✔</td><td>✔</td><td>✔</td>
</tr> </tr>
Expand Down

0 comments on commit 9336648

Please sign in to comment.