diff --git a/android/src/main/java/com/ryanheise/audioservice/AudioService.java b/android/src/main/java/com/ryanheise/audioservice/AudioService.java index adb051a..53681fa 100644 --- a/android/src/main/java/com/ryanheise/audioservice/AudioService.java +++ b/android/src/main/java/com/ryanheise/audioservice/AudioService.java @@ -402,6 +402,7 @@ public class AudioService extends MediaBrowserServiceCompat { } } } + if (displayTitle != null) builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, displayTitle); if (displaySubtitle != null) @@ -553,6 +554,7 @@ public class AudioService extends MediaBrowserServiceCompat { } public class MediaSessionCallback extends MediaSessionCompat.Callback { + @Override public void onAddQueueItem(MediaDescriptionCompat description) { if (listener == null) return; diff --git a/android/src/main/java/com/ryanheise/audioservice/AudioServicePlugin.java b/android/src/main/java/com/ryanheise/audioservice/AudioServicePlugin.java index eb04bd5..64922a8 100644 --- a/android/src/main/java/com/ryanheise/audioservice/AudioServicePlugin.java +++ b/android/src/main/java/com/ryanheise/audioservice/AudioServicePlugin.java @@ -60,6 +60,7 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor.DartCallback; import android.content.res.AssetManager; +import android.util.Log; import io.flutter.view.FlutterNativeView; import io.flutter.view.FlutterRunArguments; diff --git a/android/src/main/res/drawable-hdpi/ic_favorite.png b/android/src/main/res/drawable-hdpi/ic_favorite.png new file mode 100644 index 0000000..dceaa5d Binary files /dev/null and b/android/src/main/res/drawable-hdpi/ic_favorite.png differ diff --git a/android/src/main/res/drawable-mdpi/ic_favorite.png b/android/src/main/res/drawable-mdpi/ic_favorite.png new file mode 100644 index 0000000..7770fd5 Binary files /dev/null and b/android/src/main/res/drawable-mdpi/ic_favorite.png differ diff --git a/android/src/main/res/drawable-xhdpi/ic_favorite.png b/android/src/main/res/drawable-xhdpi/ic_favorite.png new file mode 100644 index 0000000..a0bcb2e Binary files /dev/null and b/android/src/main/res/drawable-xhdpi/ic_favorite.png differ diff --git a/android/src/main/res/drawable-xxhdpi/ic_favorite.png b/android/src/main/res/drawable-xxhdpi/ic_favorite.png new file mode 100644 index 0000000..5baaadc Binary files /dev/null and b/android/src/main/res/drawable-xxhdpi/ic_favorite.png differ diff --git a/android/src/main/res/drawable-xxxhdpi/ic_favorite.png b/android/src/main/res/drawable-xxxhdpi/ic_favorite.png new file mode 100644 index 0000000..b635760 Binary files /dev/null and b/android/src/main/res/drawable-xxxhdpi/ic_favorite.png differ diff --git a/ios/Classes/AudioServicePlugin.m b/ios/Classes/AudioServicePlugin.m deleted file mode 100644 index 48e8463..0000000 --- a/ios/Classes/AudioServicePlugin.m +++ /dev/null @@ -1,617 +0,0 @@ -#import "AudioServicePlugin.h" -#import -#import - -// If you'd like to help, please see the TODO comments below, then open a -// GitHub issue to announce your intention to work on a particular feature, and -// submit a pull request. We have an open discussion over at issue #10 about -// all things iOS if you'd like to discuss approaches or ask for input. Thank -// you for your support! - -@implementation AudioServicePlugin - -static FlutterMethodChannel *channel = nil; -static FlutterMethodChannel *backgroundChannel = nil; -static BOOL _running = NO; -static FlutterResult startResult = nil; -static MPRemoteCommandCenter *commandCenter = nil; -static NSArray *queue = nil; -static NSMutableDictionary *mediaItem = nil; -static long actionBits; -static NSArray *commands; -static BOOL _controlsUpdated = NO; -static enum AudioProcessingState processingState = none; -static BOOL playing = NO; -static NSNumber *position = nil; -static NSNumber *bufferedPosition = nil; -static NSNumber *updateTime = nil; -static NSNumber *speed = nil; -static NSNumber *repeatMode = nil; -static NSNumber *shuffleMode = nil; -static NSNumber *fastForwardInterval = nil; -static NSNumber *rewindInterval = nil; -static NSMutableDictionary *params = nil; -static MPMediaItemArtwork* artwork = nil; - -+ (void)registerWithRegistrar:(NSObject*)registrar { - @synchronized(self) { - // TODO: Need a reliable way to detect whether this is the client - // or background. - // TODO: Handle multiple clients. - // As no separate isolate is used on macOS, add both handlers to the one registrar. -#if TARGET_OS_IPHONE - if (channel == nil) { -#endif - AudioServicePlugin *instance = [[AudioServicePlugin alloc] init:registrar]; - channel = [FlutterMethodChannel - methodChannelWithName:@"ryanheise.com/audioService" - binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; -#if TARGET_OS_IPHONE - } else { - AudioServicePlugin *instance = [[AudioServicePlugin alloc] init:registrar]; -#endif - backgroundChannel = [FlutterMethodChannel - methodChannelWithName:@"ryanheise.com/audioServiceBackground" - binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:backgroundChannel]; -#if TARGET_OS_IPHONE - } -#endif - } -} - -- (instancetype)init:(NSObject *)registrar { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - return self; -} - -- (void)broadcastPlaybackState { - [channel invokeMethod:@"onPlaybackStateChanged" arguments:@[ - // processingState - @(processingState), - // playing - @(playing), - // actions - @(actionBits), - // position - position, - // bufferedPosition - bufferedPosition, - // playback speed - speed, - // update time since epoch - updateTime, - // repeat mode - repeatMode, - // shuffle mode - shuffleMode, - ]]; -} - -- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - // TODO: - // - Restructure this so that we have a separate method call delegate - // for the client instance and the background instance so that methods - // can't be called on the wrong instance. - if ([@"connect" isEqualToString:call.method]) { - long long msSinceEpoch = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); - if (position == nil) { - position = @(0); - bufferedPosition = @(0); - updateTime = [NSNumber numberWithLongLong: msSinceEpoch]; - speed = [NSNumber numberWithDouble: 1.0]; - repeatMode = @(0); - shuffleMode = @(0); - } - // Notify client of state on subscribing. - [self broadcastPlaybackState]; - [channel invokeMethod:@"onMediaChanged" arguments:@[mediaItem ? mediaItem : [NSNull null]]]; - [channel invokeMethod:@"onQueueChanged" arguments:@[queue ? queue : [NSNull null]]]; - - result(nil); - } else if ([@"disconnect" isEqualToString:call.method]) { - result(nil); - } else if ([@"start" isEqualToString:call.method]) { - if (_running) { - result(@NO); - return; - } - _running = YES; - // The result will be sent after the background task actually starts. - // See the "ready" case below. - startResult = result; - -#if TARGET_OS_IPHONE - [AVAudioSession sharedInstance]; -#endif - - // Set callbacks on MPRemoteCommandCenter - fastForwardInterval = [call.arguments objectForKey:@"fastForwardInterval"]; - rewindInterval = [call.arguments objectForKey:@"rewindInterval"]; - commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; - commands = @[ - commandCenter.stopCommand, - commandCenter.pauseCommand, - commandCenter.playCommand, - commandCenter.skipBackwardCommand, - commandCenter.previousTrackCommand, - commandCenter.nextTrackCommand, - commandCenter.skipForwardCommand, - [NSNull null], - commandCenter.changePlaybackPositionCommand, - commandCenter.togglePlayPauseCommand, - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - commandCenter.changeRepeatModeCommand, - [NSNull null], - [NSNull null], - commandCenter.changeShuffleModeCommand, - commandCenter.seekBackwardCommand, - commandCenter.seekForwardCommand, - ]; - [commandCenter.changePlaybackRateCommand setEnabled:YES]; - [commandCenter.togglePlayPauseCommand setEnabled:YES]; - [commandCenter.togglePlayPauseCommand addTarget:self action:@selector(togglePlayPause:)]; - // TODO: enable more commands - // Language options - if (@available(iOS 9.0, macOS 10.12.2, *)) { - [commandCenter.enableLanguageOptionCommand setEnabled:NO]; - [commandCenter.disableLanguageOptionCommand setEnabled:NO]; - } - // Rating - [commandCenter.ratingCommand setEnabled:NO]; - // Feedback - [commandCenter.likeCommand setEnabled:NO]; - [commandCenter.dislikeCommand setEnabled:NO]; - [commandCenter.bookmarkCommand setEnabled:NO]; - [self updateControls]; - - // Params - params = [call.arguments objectForKey:@"params"]; - -#if TARGET_OS_OSX - // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. - // We send a result here, and then the Dart code continues in the main isolate. - result(@YES); -#endif - } else if ([@"ready" isEqualToString:call.method]) { - NSMutableDictionary *startParams = [NSMutableDictionary new]; - startParams[@"fastForwardInterval"] = fastForwardInterval; - startParams[@"rewindInterval"] = rewindInterval; - startParams[@"params"] = params; - result(startParams); - } else if ([@"started" isEqualToString:call.method]) { -#if TARGET_OS_IPHONE - if (startResult) { - startResult(@YES); - startResult = nil; - } -#endif - result(@YES); - } else if ([@"stopped" isEqualToString:call.method]) { - _running = NO; - [channel invokeMethod:@"onStopped" arguments:nil]; - [commandCenter.changePlaybackRateCommand setEnabled:NO]; - [commandCenter.togglePlayPauseCommand setEnabled:NO]; - [commandCenter.togglePlayPauseCommand removeTarget:nil]; - [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nil; - processingState = none; - playing = NO; - position = nil; - bufferedPosition = nil; - updateTime = nil; - speed = nil; - artwork = nil; - mediaItem = nil; - repeatMode = @(0); - shuffleMode = @(0); - actionBits = 0; - [self updateControls]; - _controlsUpdated = NO; - queue = nil; - startResult = nil; - fastForwardInterval = nil; - rewindInterval = nil; - params = nil; - commandCenter = nil; - result(@YES); - } else if ([@"isRunning" isEqualToString:call.method]) { - if (_running) { - result(@YES); - } else { - result(@NO); - } - } else if ([@"setBrowseMediaParent" isEqualToString:call.method]) { - result(@YES); - } else if ([@"addQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onAddQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"addQueueItemAt" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onAddQueueItemAt" arguments:call.arguments result: result]; - } else if ([@"removeQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onRemoveQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"updateQueue" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onUpdateQueue" arguments:@[call.arguments] result: result]; - } else if ([@"updateMediaItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onUpdateMediaItem" arguments:@[call.arguments] result: result]; - } else if ([@"click" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onClick" arguments:@[call.arguments] result: result]; - } else if ([@"prepare" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPrepare" arguments:nil result: result]; - } else if ([@"prepareFromMediaId" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPrepareFromMediaId" arguments:@[call.arguments] result: result]; - } else if ([@"play" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlay" arguments:nil result: result]; - } else if ([@"playFromMediaId" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlayFromMediaId" arguments:@[call.arguments] result: result]; - } else if ([@"playMediaItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlayMediaItem" arguments:@[call.arguments] result: result]; - } else if ([@"skipToQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"pause" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPause" arguments:nil result: result]; - } else if ([@"stop" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onStop" arguments:nil result: result]; - } else if ([@"seekTo" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekTo" arguments:@[call.arguments] result: result]; - } else if ([@"skipToNext" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToNext" arguments:nil result: result]; - } else if ([@"skipToPrevious" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToPrevious" arguments:nil result: result]; - } else if ([@"fastForward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onFastForward" arguments:nil result: result]; - } else if ([@"rewind" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onRewind" arguments:nil result: result]; - } else if ([@"setRepeatMode" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetRepeatMode" arguments:@[call.arguments] result: result]; - } else if ([@"setShuffleMode" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetShuffleMode" arguments:@[call.arguments] result: result]; - } else if ([@"setRating" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetRating" arguments:@[call.arguments[@"rating"], call.arguments[@"extras"]] result: result]; - } else if ([@"setSpeed" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetSpeed" arguments:@[call.arguments] result: result]; - } else if ([@"seekForward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekForward" arguments:@[call.arguments] result: result]; - } else if ([@"seekBackward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekBackward" arguments:@[call.arguments] result: result]; - } else if ([@"setState" isEqualToString:call.method]) { - long long msSinceEpoch; - if (call.arguments[7] != [NSNull null]) { - msSinceEpoch = [call.arguments[7] longLongValue]; - } else { - msSinceEpoch = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); - } - actionBits = 0; - NSArray *controlsArray = call.arguments[0]; - for (int i = 0; i < controlsArray.count; i++) { - NSDictionary *control = (NSDictionary *)controlsArray[i]; - NSNumber *actionIndex = (NSNumber *)control[@"action"]; - int actionCode = 1 << [actionIndex intValue]; - actionBits |= actionCode; - } - NSArray *systemActionsArray = call.arguments[1]; - for (int i = 0; i < systemActionsArray.count; i++) { - NSNumber *actionIndex = (NSNumber *)systemActionsArray[i]; - int actionCode = 1 << [actionIndex intValue]; - actionBits |= actionCode; - } - processingState = [call.arguments[2] intValue]; - playing = [call.arguments[3] boolValue]; - position = call.arguments[4]; - bufferedPosition = call.arguments[5]; - speed = call.arguments[6]; - repeatMode = call.arguments[9]; - shuffleMode = call.arguments[10]; - updateTime = [NSNumber numberWithLongLong: msSinceEpoch]; - [self broadcastPlaybackState]; - [self updateControls]; - [self updateNowPlayingInfo]; - result(@(YES)); - } else if ([@"setQueue" isEqualToString:call.method]) { - queue = call.arguments; - [channel invokeMethod:@"onQueueChanged" arguments:@[queue]]; - result(@YES); - } else if ([@"setMediaItem" isEqualToString:call.method]) { - mediaItem = call.arguments; - NSString* artUri = mediaItem[@"artUri"]; - artwork = nil; - if (![artUri isEqual: [NSNull null]]) { - NSString* artCacheFilePath = [NSNull null]; - NSDictionary* extras = mediaItem[@"extras"]; - if (![extras isEqual: [NSNull null]]) { - artCacheFilePath = extras[@"artCacheFile"]; - } - if (![artCacheFilePath isEqual: [NSNull null]]) { -#if TARGET_OS_IPHONE - UIImage* artImage = [UIImage imageWithContentsOfFile:artCacheFilePath]; -#else - NSImage* artImage = [[NSImage alloc] initWithContentsOfFile:artCacheFilePath]; -#endif - if (artImage != nil) { -#if TARGET_OS_IPHONE - artwork = [[MPMediaItemArtwork alloc] initWithImage: artImage]; -#else - artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:artImage.size requestHandler:^NSImage* _Nonnull(CGSize aSize) { - return artImage; - }]; -#endif - } - } - } - [self updateNowPlayingInfo]; - [channel invokeMethod:@"onMediaChanged" arguments:@[call.arguments]]; - result(@(YES)); - } else if ([@"notifyChildrenChanged" isEqualToString:call.method]) { - result(@YES); - } else if ([@"androidForceEnableMediaButtons" isEqualToString:call.method]) { - result(@YES); - } else { - // TODO: Check if this implementation is correct. - // Can I just pass on the result as the last argument? - [backgroundChannel invokeMethod:call.method arguments:call.arguments result: result]; - } -} - -- (MPRemoteCommandHandlerStatus) play: (MPRemoteCommandEvent *) event { - NSLog(@"play"); - [backgroundChannel invokeMethod:@"onPlay" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) pause: (MPRemoteCommandEvent *) event { - NSLog(@"pause"); - [backgroundChannel invokeMethod:@"onPause" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (void) updateNowPlayingInfo { - NSMutableDictionary *nowPlayingInfo = [NSMutableDictionary new]; - if (mediaItem) { - nowPlayingInfo[MPMediaItemPropertyTitle] = mediaItem[@"title"]; - nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = mediaItem[@"album"]; - if (mediaItem[@"artist"] != [NSNull null]) { - nowPlayingInfo[MPMediaItemPropertyArtist] = mediaItem[@"artist"]; - } - if (mediaItem[@"duration"] != [NSNull null]) { - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithLongLong: ([mediaItem[@"duration"] longLongValue] / 1000)]; - } - if (@available(iOS 3.0, macOS 10.13.2, *)) { - if (artwork) { - nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork; - } - } - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithInt:([position intValue] / 1000)]; - } - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithDouble: playing ? 1.0 : 0.0]; - [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo; -} - -- (void) updateControls { - for (enum MediaAction action = AStop; action <= ASeekForward; action++) { - [self updateControl:action]; - } - _controlsUpdated = YES; -} - -- (void) updateControl:(enum MediaAction)action { - MPRemoteCommand *command = commands[action]; - if (command == [NSNull null]) return; - // Shift the actionBits right until the least significant bit is the tested action bit, and AND that with a 1 at the same position. - // All bytes become 0, other than the tested action bit, which will be 0 or 1 according to its status in the actionBits long. - BOOL enable = ((actionBits >> action) & 1); - if (_controlsUpdated && enable == command.enabled) return; - [command setEnabled:enable]; - switch (action) { - case AStop: - if (enable) { - [commandCenter.stopCommand addTarget:self action:@selector(stop:)]; - } else { - [commandCenter.stopCommand removeTarget:nil]; - } - break; - case APause: - if (enable) { - [commandCenter.pauseCommand addTarget:self action:@selector(pause:)]; - } else { - [commandCenter.pauseCommand removeTarget:nil]; - } - break; - case APlay: - if (enable) { - [commandCenter.playCommand addTarget:self action:@selector(play:)]; - } else { - [commandCenter.playCommand removeTarget:nil]; - } - break; - case ARewind: - if (rewindInterval.integerValue > 0) { - if (enable) { - [commandCenter.skipBackwardCommand addTarget: self action:@selector(skipBackward:)]; - int rewindIntervalInSeconds = [rewindInterval intValue]/1000; - NSNumber *rewindIntervalInSec = [NSNumber numberWithInt: rewindIntervalInSeconds]; - commandCenter.skipBackwardCommand.preferredIntervals = @[rewindIntervalInSec]; - } else { - [commandCenter.skipBackwardCommand removeTarget:nil]; - } - } - break; - case ASkipToPrevious: - if (enable) { - [commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrack:)]; - } else { - [commandCenter.previousTrackCommand removeTarget:nil]; - } - break; - case ASkipToNext: - if (enable) { - [commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrack:)]; - } else { - [commandCenter.nextTrackCommand removeTarget:nil]; - } - break; - case AFastForward: - if (fastForwardInterval.integerValue > 0) { - if (enable) { - [commandCenter.skipForwardCommand addTarget: self action:@selector(skipForward:)]; - int fastForwardIntervalInSeconds = [fastForwardInterval intValue]/1000; - NSNumber *fastForwardIntervalInSec = [NSNumber numberWithInt: fastForwardIntervalInSeconds]; - commandCenter.skipForwardCommand.preferredIntervals = @[fastForwardIntervalInSec]; - } else { - [commandCenter.skipForwardCommand removeTarget:nil]; - } - } - break; - case ASetRating: - // TODO: - // commandCenter.ratingCommand - // commandCenter.dislikeCommand - // commandCenter.bookmarkCommand - break; - case ASeekTo: - if (@available(iOS 9.1, macOS 10.12.2, *)) { - if (enable) { - [commandCenter.changePlaybackPositionCommand addTarget:self action:@selector(changePlaybackPosition:)]; - } else { - [commandCenter.changePlaybackPositionCommand removeTarget:nil]; - } - } - case APlayPause: - // Automatically enabled. - break; - case ASetRepeatMode: - if (enable) { - [commandCenter.changeRepeatModeCommand addTarget:self action:@selector(changeRepeatMode:)]; - } else { - [commandCenter.changeRepeatModeCommand removeTarget:nil]; - } - break; - case ASetShuffleMode: - if (enable) { - [commandCenter.changeShuffleModeCommand addTarget:self action:@selector(changeShuffleMode:)]; - } else { - [commandCenter.changeShuffleModeCommand removeTarget:nil]; - } - break; - case ASeekBackward: - if (enable) { - [commandCenter.seekBackwardCommand addTarget:self action:@selector(seekBackward:)]; - } else { - [commandCenter.seekBackwardCommand removeTarget:nil]; - } - break; - case ASeekForward: - if (enable) { - [commandCenter.seekForwardCommand addTarget:self action:@selector(seekForward:)]; - } else { - [commandCenter.seekForwardCommand removeTarget:nil]; - } - break; - } -} - -- (MPRemoteCommandHandlerStatus) togglePlayPause: (MPRemoteCommandEvent *) event { - NSLog(@"togglePlayPause"); - [backgroundChannel invokeMethod:@"onClick" arguments:@[@(0)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) stop: (MPRemoteCommandEvent *) event { - NSLog(@"stop"); - [backgroundChannel invokeMethod:@"onStop" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) nextTrack: (MPRemoteCommandEvent *) event { - NSLog(@"nextTrack"); - [backgroundChannel invokeMethod:@"onSkipToNext" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) previousTrack: (MPRemoteCommandEvent *) event { - NSLog(@"previousTrack"); - [backgroundChannel invokeMethod:@"onSkipToPrevious" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changePlaybackPosition: (MPChangePlaybackPositionCommandEvent *) event { - NSLog(@"changePlaybackPosition"); - [backgroundChannel invokeMethod:@"onSeekTo" arguments: @[@((long long) (event.positionTime * 1000))]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) skipForward: (MPRemoteCommandEvent *) event { - NSLog(@"skipForward"); - [backgroundChannel invokeMethod:@"onFastForward" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) skipBackward: (MPRemoteCommandEvent *) event { - NSLog(@"skipBackward"); - [backgroundChannel invokeMethod:@"onRewind" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) seekForward: (MPSeekCommandEvent *) event { - NSLog(@"seekForward"); - BOOL begin = event.type == MPSeekCommandEventTypeBeginSeeking; - [backgroundChannel invokeMethod:@"onSeekForward" arguments:@[@(begin)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) seekBackward: (MPSeekCommandEvent *) event { - NSLog(@"seekBackward"); - BOOL begin = event.type == MPSeekCommandEventTypeBeginSeeking; - [backgroundChannel invokeMethod:@"onSeekBackward" arguments:@[@(begin)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changeRepeatMode: (MPChangeRepeatModeCommandEvent *) event { - NSLog(@"changeRepeatMode"); - int modeIndex; - switch (event.repeatType) { - case MPRepeatTypeOff: - modeIndex = 0; - break; - case MPRepeatTypeOne: - modeIndex = 1; - break; - // MPRepeatTypeAll - default: - modeIndex = 2; - break; - } - [backgroundChannel invokeMethod:@"onSetRepeatMode" arguments:@[@(modeIndex)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changeShuffleMode: (MPChangeShuffleModeCommandEvent *) event { - NSLog(@"changeShuffleMode"); - int modeIndex; - switch (event.shuffleType) { - case MPShuffleTypeOff: - modeIndex = 0; - break; - case MPShuffleTypeItems: - modeIndex = 1; - break; - // MPShuffleTypeCollections - default: - modeIndex = 2; - break; - } - [backgroundChannel invokeMethod:@"onSetShuffleMode" arguments:@[@(modeIndex)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (void) dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -@end diff --git a/ios/Classes/AudioServicePlugin.m b/ios/Classes/AudioServicePlugin.m new file mode 120000 index 0000000..ce7a5fe --- /dev/null +++ b/ios/Classes/AudioServicePlugin.m @@ -0,0 +1 @@ +../../darwin/Classes/AudioServicePlugin.m \ No newline at end of file diff --git a/macos/Classes/AudioServicePlugin.m b/macos/Classes/AudioServicePlugin.m deleted file mode 100644 index 48e8463..0000000 --- a/macos/Classes/AudioServicePlugin.m +++ /dev/null @@ -1,617 +0,0 @@ -#import "AudioServicePlugin.h" -#import -#import - -// If you'd like to help, please see the TODO comments below, then open a -// GitHub issue to announce your intention to work on a particular feature, and -// submit a pull request. We have an open discussion over at issue #10 about -// all things iOS if you'd like to discuss approaches or ask for input. Thank -// you for your support! - -@implementation AudioServicePlugin - -static FlutterMethodChannel *channel = nil; -static FlutterMethodChannel *backgroundChannel = nil; -static BOOL _running = NO; -static FlutterResult startResult = nil; -static MPRemoteCommandCenter *commandCenter = nil; -static NSArray *queue = nil; -static NSMutableDictionary *mediaItem = nil; -static long actionBits; -static NSArray *commands; -static BOOL _controlsUpdated = NO; -static enum AudioProcessingState processingState = none; -static BOOL playing = NO; -static NSNumber *position = nil; -static NSNumber *bufferedPosition = nil; -static NSNumber *updateTime = nil; -static NSNumber *speed = nil; -static NSNumber *repeatMode = nil; -static NSNumber *shuffleMode = nil; -static NSNumber *fastForwardInterval = nil; -static NSNumber *rewindInterval = nil; -static NSMutableDictionary *params = nil; -static MPMediaItemArtwork* artwork = nil; - -+ (void)registerWithRegistrar:(NSObject*)registrar { - @synchronized(self) { - // TODO: Need a reliable way to detect whether this is the client - // or background. - // TODO: Handle multiple clients. - // As no separate isolate is used on macOS, add both handlers to the one registrar. -#if TARGET_OS_IPHONE - if (channel == nil) { -#endif - AudioServicePlugin *instance = [[AudioServicePlugin alloc] init:registrar]; - channel = [FlutterMethodChannel - methodChannelWithName:@"ryanheise.com/audioService" - binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; -#if TARGET_OS_IPHONE - } else { - AudioServicePlugin *instance = [[AudioServicePlugin alloc] init:registrar]; -#endif - backgroundChannel = [FlutterMethodChannel - methodChannelWithName:@"ryanheise.com/audioServiceBackground" - binaryMessenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:backgroundChannel]; -#if TARGET_OS_IPHONE - } -#endif - } -} - -- (instancetype)init:(NSObject *)registrar { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - return self; -} - -- (void)broadcastPlaybackState { - [channel invokeMethod:@"onPlaybackStateChanged" arguments:@[ - // processingState - @(processingState), - // playing - @(playing), - // actions - @(actionBits), - // position - position, - // bufferedPosition - bufferedPosition, - // playback speed - speed, - // update time since epoch - updateTime, - // repeat mode - repeatMode, - // shuffle mode - shuffleMode, - ]]; -} - -- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - // TODO: - // - Restructure this so that we have a separate method call delegate - // for the client instance and the background instance so that methods - // can't be called on the wrong instance. - if ([@"connect" isEqualToString:call.method]) { - long long msSinceEpoch = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); - if (position == nil) { - position = @(0); - bufferedPosition = @(0); - updateTime = [NSNumber numberWithLongLong: msSinceEpoch]; - speed = [NSNumber numberWithDouble: 1.0]; - repeatMode = @(0); - shuffleMode = @(0); - } - // Notify client of state on subscribing. - [self broadcastPlaybackState]; - [channel invokeMethod:@"onMediaChanged" arguments:@[mediaItem ? mediaItem : [NSNull null]]]; - [channel invokeMethod:@"onQueueChanged" arguments:@[queue ? queue : [NSNull null]]]; - - result(nil); - } else if ([@"disconnect" isEqualToString:call.method]) { - result(nil); - } else if ([@"start" isEqualToString:call.method]) { - if (_running) { - result(@NO); - return; - } - _running = YES; - // The result will be sent after the background task actually starts. - // See the "ready" case below. - startResult = result; - -#if TARGET_OS_IPHONE - [AVAudioSession sharedInstance]; -#endif - - // Set callbacks on MPRemoteCommandCenter - fastForwardInterval = [call.arguments objectForKey:@"fastForwardInterval"]; - rewindInterval = [call.arguments objectForKey:@"rewindInterval"]; - commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; - commands = @[ - commandCenter.stopCommand, - commandCenter.pauseCommand, - commandCenter.playCommand, - commandCenter.skipBackwardCommand, - commandCenter.previousTrackCommand, - commandCenter.nextTrackCommand, - commandCenter.skipForwardCommand, - [NSNull null], - commandCenter.changePlaybackPositionCommand, - commandCenter.togglePlayPauseCommand, - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - [NSNull null], - commandCenter.changeRepeatModeCommand, - [NSNull null], - [NSNull null], - commandCenter.changeShuffleModeCommand, - commandCenter.seekBackwardCommand, - commandCenter.seekForwardCommand, - ]; - [commandCenter.changePlaybackRateCommand setEnabled:YES]; - [commandCenter.togglePlayPauseCommand setEnabled:YES]; - [commandCenter.togglePlayPauseCommand addTarget:self action:@selector(togglePlayPause:)]; - // TODO: enable more commands - // Language options - if (@available(iOS 9.0, macOS 10.12.2, *)) { - [commandCenter.enableLanguageOptionCommand setEnabled:NO]; - [commandCenter.disableLanguageOptionCommand setEnabled:NO]; - } - // Rating - [commandCenter.ratingCommand setEnabled:NO]; - // Feedback - [commandCenter.likeCommand setEnabled:NO]; - [commandCenter.dislikeCommand setEnabled:NO]; - [commandCenter.bookmarkCommand setEnabled:NO]; - [self updateControls]; - - // Params - params = [call.arguments objectForKey:@"params"]; - -#if TARGET_OS_OSX - // No isolate can be used for macOS until https://github.com/flutter/flutter/issues/65222 is resolved. - // We send a result here, and then the Dart code continues in the main isolate. - result(@YES); -#endif - } else if ([@"ready" isEqualToString:call.method]) { - NSMutableDictionary *startParams = [NSMutableDictionary new]; - startParams[@"fastForwardInterval"] = fastForwardInterval; - startParams[@"rewindInterval"] = rewindInterval; - startParams[@"params"] = params; - result(startParams); - } else if ([@"started" isEqualToString:call.method]) { -#if TARGET_OS_IPHONE - if (startResult) { - startResult(@YES); - startResult = nil; - } -#endif - result(@YES); - } else if ([@"stopped" isEqualToString:call.method]) { - _running = NO; - [channel invokeMethod:@"onStopped" arguments:nil]; - [commandCenter.changePlaybackRateCommand setEnabled:NO]; - [commandCenter.togglePlayPauseCommand setEnabled:NO]; - [commandCenter.togglePlayPauseCommand removeTarget:nil]; - [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nil; - processingState = none; - playing = NO; - position = nil; - bufferedPosition = nil; - updateTime = nil; - speed = nil; - artwork = nil; - mediaItem = nil; - repeatMode = @(0); - shuffleMode = @(0); - actionBits = 0; - [self updateControls]; - _controlsUpdated = NO; - queue = nil; - startResult = nil; - fastForwardInterval = nil; - rewindInterval = nil; - params = nil; - commandCenter = nil; - result(@YES); - } else if ([@"isRunning" isEqualToString:call.method]) { - if (_running) { - result(@YES); - } else { - result(@NO); - } - } else if ([@"setBrowseMediaParent" isEqualToString:call.method]) { - result(@YES); - } else if ([@"addQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onAddQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"addQueueItemAt" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onAddQueueItemAt" arguments:call.arguments result: result]; - } else if ([@"removeQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onRemoveQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"updateQueue" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onUpdateQueue" arguments:@[call.arguments] result: result]; - } else if ([@"updateMediaItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onUpdateMediaItem" arguments:@[call.arguments] result: result]; - } else if ([@"click" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onClick" arguments:@[call.arguments] result: result]; - } else if ([@"prepare" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPrepare" arguments:nil result: result]; - } else if ([@"prepareFromMediaId" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPrepareFromMediaId" arguments:@[call.arguments] result: result]; - } else if ([@"play" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlay" arguments:nil result: result]; - } else if ([@"playFromMediaId" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlayFromMediaId" arguments:@[call.arguments] result: result]; - } else if ([@"playMediaItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPlayMediaItem" arguments:@[call.arguments] result: result]; - } else if ([@"skipToQueueItem" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToQueueItem" arguments:@[call.arguments] result: result]; - } else if ([@"pause" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onPause" arguments:nil result: result]; - } else if ([@"stop" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onStop" arguments:nil result: result]; - } else if ([@"seekTo" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekTo" arguments:@[call.arguments] result: result]; - } else if ([@"skipToNext" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToNext" arguments:nil result: result]; - } else if ([@"skipToPrevious" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSkipToPrevious" arguments:nil result: result]; - } else if ([@"fastForward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onFastForward" arguments:nil result: result]; - } else if ([@"rewind" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onRewind" arguments:nil result: result]; - } else if ([@"setRepeatMode" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetRepeatMode" arguments:@[call.arguments] result: result]; - } else if ([@"setShuffleMode" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetShuffleMode" arguments:@[call.arguments] result: result]; - } else if ([@"setRating" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetRating" arguments:@[call.arguments[@"rating"], call.arguments[@"extras"]] result: result]; - } else if ([@"setSpeed" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSetSpeed" arguments:@[call.arguments] result: result]; - } else if ([@"seekForward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekForward" arguments:@[call.arguments] result: result]; - } else if ([@"seekBackward" isEqualToString:call.method]) { - [backgroundChannel invokeMethod:@"onSeekBackward" arguments:@[call.arguments] result: result]; - } else if ([@"setState" isEqualToString:call.method]) { - long long msSinceEpoch; - if (call.arguments[7] != [NSNull null]) { - msSinceEpoch = [call.arguments[7] longLongValue]; - } else { - msSinceEpoch = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); - } - actionBits = 0; - NSArray *controlsArray = call.arguments[0]; - for (int i = 0; i < controlsArray.count; i++) { - NSDictionary *control = (NSDictionary *)controlsArray[i]; - NSNumber *actionIndex = (NSNumber *)control[@"action"]; - int actionCode = 1 << [actionIndex intValue]; - actionBits |= actionCode; - } - NSArray *systemActionsArray = call.arguments[1]; - for (int i = 0; i < systemActionsArray.count; i++) { - NSNumber *actionIndex = (NSNumber *)systemActionsArray[i]; - int actionCode = 1 << [actionIndex intValue]; - actionBits |= actionCode; - } - processingState = [call.arguments[2] intValue]; - playing = [call.arguments[3] boolValue]; - position = call.arguments[4]; - bufferedPosition = call.arguments[5]; - speed = call.arguments[6]; - repeatMode = call.arguments[9]; - shuffleMode = call.arguments[10]; - updateTime = [NSNumber numberWithLongLong: msSinceEpoch]; - [self broadcastPlaybackState]; - [self updateControls]; - [self updateNowPlayingInfo]; - result(@(YES)); - } else if ([@"setQueue" isEqualToString:call.method]) { - queue = call.arguments; - [channel invokeMethod:@"onQueueChanged" arguments:@[queue]]; - result(@YES); - } else if ([@"setMediaItem" isEqualToString:call.method]) { - mediaItem = call.arguments; - NSString* artUri = mediaItem[@"artUri"]; - artwork = nil; - if (![artUri isEqual: [NSNull null]]) { - NSString* artCacheFilePath = [NSNull null]; - NSDictionary* extras = mediaItem[@"extras"]; - if (![extras isEqual: [NSNull null]]) { - artCacheFilePath = extras[@"artCacheFile"]; - } - if (![artCacheFilePath isEqual: [NSNull null]]) { -#if TARGET_OS_IPHONE - UIImage* artImage = [UIImage imageWithContentsOfFile:artCacheFilePath]; -#else - NSImage* artImage = [[NSImage alloc] initWithContentsOfFile:artCacheFilePath]; -#endif - if (artImage != nil) { -#if TARGET_OS_IPHONE - artwork = [[MPMediaItemArtwork alloc] initWithImage: artImage]; -#else - artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:artImage.size requestHandler:^NSImage* _Nonnull(CGSize aSize) { - return artImage; - }]; -#endif - } - } - } - [self updateNowPlayingInfo]; - [channel invokeMethod:@"onMediaChanged" arguments:@[call.arguments]]; - result(@(YES)); - } else if ([@"notifyChildrenChanged" isEqualToString:call.method]) { - result(@YES); - } else if ([@"androidForceEnableMediaButtons" isEqualToString:call.method]) { - result(@YES); - } else { - // TODO: Check if this implementation is correct. - // Can I just pass on the result as the last argument? - [backgroundChannel invokeMethod:call.method arguments:call.arguments result: result]; - } -} - -- (MPRemoteCommandHandlerStatus) play: (MPRemoteCommandEvent *) event { - NSLog(@"play"); - [backgroundChannel invokeMethod:@"onPlay" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) pause: (MPRemoteCommandEvent *) event { - NSLog(@"pause"); - [backgroundChannel invokeMethod:@"onPause" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (void) updateNowPlayingInfo { - NSMutableDictionary *nowPlayingInfo = [NSMutableDictionary new]; - if (mediaItem) { - nowPlayingInfo[MPMediaItemPropertyTitle] = mediaItem[@"title"]; - nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = mediaItem[@"album"]; - if (mediaItem[@"artist"] != [NSNull null]) { - nowPlayingInfo[MPMediaItemPropertyArtist] = mediaItem[@"artist"]; - } - if (mediaItem[@"duration"] != [NSNull null]) { - nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithLongLong: ([mediaItem[@"duration"] longLongValue] / 1000)]; - } - if (@available(iOS 3.0, macOS 10.13.2, *)) { - if (artwork) { - nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork; - } - } - nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithInt:([position intValue] / 1000)]; - } - nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithDouble: playing ? 1.0 : 0.0]; - [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo; -} - -- (void) updateControls { - for (enum MediaAction action = AStop; action <= ASeekForward; action++) { - [self updateControl:action]; - } - _controlsUpdated = YES; -} - -- (void) updateControl:(enum MediaAction)action { - MPRemoteCommand *command = commands[action]; - if (command == [NSNull null]) return; - // Shift the actionBits right until the least significant bit is the tested action bit, and AND that with a 1 at the same position. - // All bytes become 0, other than the tested action bit, which will be 0 or 1 according to its status in the actionBits long. - BOOL enable = ((actionBits >> action) & 1); - if (_controlsUpdated && enable == command.enabled) return; - [command setEnabled:enable]; - switch (action) { - case AStop: - if (enable) { - [commandCenter.stopCommand addTarget:self action:@selector(stop:)]; - } else { - [commandCenter.stopCommand removeTarget:nil]; - } - break; - case APause: - if (enable) { - [commandCenter.pauseCommand addTarget:self action:@selector(pause:)]; - } else { - [commandCenter.pauseCommand removeTarget:nil]; - } - break; - case APlay: - if (enable) { - [commandCenter.playCommand addTarget:self action:@selector(play:)]; - } else { - [commandCenter.playCommand removeTarget:nil]; - } - break; - case ARewind: - if (rewindInterval.integerValue > 0) { - if (enable) { - [commandCenter.skipBackwardCommand addTarget: self action:@selector(skipBackward:)]; - int rewindIntervalInSeconds = [rewindInterval intValue]/1000; - NSNumber *rewindIntervalInSec = [NSNumber numberWithInt: rewindIntervalInSeconds]; - commandCenter.skipBackwardCommand.preferredIntervals = @[rewindIntervalInSec]; - } else { - [commandCenter.skipBackwardCommand removeTarget:nil]; - } - } - break; - case ASkipToPrevious: - if (enable) { - [commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrack:)]; - } else { - [commandCenter.previousTrackCommand removeTarget:nil]; - } - break; - case ASkipToNext: - if (enable) { - [commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrack:)]; - } else { - [commandCenter.nextTrackCommand removeTarget:nil]; - } - break; - case AFastForward: - if (fastForwardInterval.integerValue > 0) { - if (enable) { - [commandCenter.skipForwardCommand addTarget: self action:@selector(skipForward:)]; - int fastForwardIntervalInSeconds = [fastForwardInterval intValue]/1000; - NSNumber *fastForwardIntervalInSec = [NSNumber numberWithInt: fastForwardIntervalInSeconds]; - commandCenter.skipForwardCommand.preferredIntervals = @[fastForwardIntervalInSec]; - } else { - [commandCenter.skipForwardCommand removeTarget:nil]; - } - } - break; - case ASetRating: - // TODO: - // commandCenter.ratingCommand - // commandCenter.dislikeCommand - // commandCenter.bookmarkCommand - break; - case ASeekTo: - if (@available(iOS 9.1, macOS 10.12.2, *)) { - if (enable) { - [commandCenter.changePlaybackPositionCommand addTarget:self action:@selector(changePlaybackPosition:)]; - } else { - [commandCenter.changePlaybackPositionCommand removeTarget:nil]; - } - } - case APlayPause: - // Automatically enabled. - break; - case ASetRepeatMode: - if (enable) { - [commandCenter.changeRepeatModeCommand addTarget:self action:@selector(changeRepeatMode:)]; - } else { - [commandCenter.changeRepeatModeCommand removeTarget:nil]; - } - break; - case ASetShuffleMode: - if (enable) { - [commandCenter.changeShuffleModeCommand addTarget:self action:@selector(changeShuffleMode:)]; - } else { - [commandCenter.changeShuffleModeCommand removeTarget:nil]; - } - break; - case ASeekBackward: - if (enable) { - [commandCenter.seekBackwardCommand addTarget:self action:@selector(seekBackward:)]; - } else { - [commandCenter.seekBackwardCommand removeTarget:nil]; - } - break; - case ASeekForward: - if (enable) { - [commandCenter.seekForwardCommand addTarget:self action:@selector(seekForward:)]; - } else { - [commandCenter.seekForwardCommand removeTarget:nil]; - } - break; - } -} - -- (MPRemoteCommandHandlerStatus) togglePlayPause: (MPRemoteCommandEvent *) event { - NSLog(@"togglePlayPause"); - [backgroundChannel invokeMethod:@"onClick" arguments:@[@(0)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) stop: (MPRemoteCommandEvent *) event { - NSLog(@"stop"); - [backgroundChannel invokeMethod:@"onStop" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) nextTrack: (MPRemoteCommandEvent *) event { - NSLog(@"nextTrack"); - [backgroundChannel invokeMethod:@"onSkipToNext" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) previousTrack: (MPRemoteCommandEvent *) event { - NSLog(@"previousTrack"); - [backgroundChannel invokeMethod:@"onSkipToPrevious" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changePlaybackPosition: (MPChangePlaybackPositionCommandEvent *) event { - NSLog(@"changePlaybackPosition"); - [backgroundChannel invokeMethod:@"onSeekTo" arguments: @[@((long long) (event.positionTime * 1000))]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) skipForward: (MPRemoteCommandEvent *) event { - NSLog(@"skipForward"); - [backgroundChannel invokeMethod:@"onFastForward" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) skipBackward: (MPRemoteCommandEvent *) event { - NSLog(@"skipBackward"); - [backgroundChannel invokeMethod:@"onRewind" arguments:nil]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) seekForward: (MPSeekCommandEvent *) event { - NSLog(@"seekForward"); - BOOL begin = event.type == MPSeekCommandEventTypeBeginSeeking; - [backgroundChannel invokeMethod:@"onSeekForward" arguments:@[@(begin)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) seekBackward: (MPSeekCommandEvent *) event { - NSLog(@"seekBackward"); - BOOL begin = event.type == MPSeekCommandEventTypeBeginSeeking; - [backgroundChannel invokeMethod:@"onSeekBackward" arguments:@[@(begin)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changeRepeatMode: (MPChangeRepeatModeCommandEvent *) event { - NSLog(@"changeRepeatMode"); - int modeIndex; - switch (event.repeatType) { - case MPRepeatTypeOff: - modeIndex = 0; - break; - case MPRepeatTypeOne: - modeIndex = 1; - break; - // MPRepeatTypeAll - default: - modeIndex = 2; - break; - } - [backgroundChannel invokeMethod:@"onSetRepeatMode" arguments:@[@(modeIndex)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (MPRemoteCommandHandlerStatus) changeShuffleMode: (MPChangeShuffleModeCommandEvent *) event { - NSLog(@"changeShuffleMode"); - int modeIndex; - switch (event.shuffleType) { - case MPShuffleTypeOff: - modeIndex = 0; - break; - case MPShuffleTypeItems: - modeIndex = 1; - break; - // MPShuffleTypeCollections - default: - modeIndex = 2; - break; - } - [backgroundChannel invokeMethod:@"onSetShuffleMode" arguments:@[@(modeIndex)]]; - return MPRemoteCommandHandlerStatusSuccess; -} - -- (void) dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -@end diff --git a/macos/Classes/AudioServicePlugin.m b/macos/Classes/AudioServicePlugin.m new file mode 120000 index 0000000..ce7a5fe --- /dev/null +++ b/macos/Classes/AudioServicePlugin.m @@ -0,0 +1 @@ +../../darwin/Classes/AudioServicePlugin.m \ No newline at end of file