For Freezer 0.5.0
This commit is contained in:
parent
73fce9905f
commit
b268066d26
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 803 B |
Binary file not shown.
After Width: | Height: | Size: 597 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,617 +0,0 @@
|
|||
#import "AudioServicePlugin.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
|
||||
// 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<FlutterPluginRegistrar>*)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<FlutterPluginRegistrar> *)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
|
|
@ -0,0 +1 @@
|
|||
../../darwin/Classes/AudioServicePlugin.m
|
|
@ -1,617 +0,0 @@
|
|||
#import "AudioServicePlugin.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
|
||||
// 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<FlutterPluginRegistrar>*)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<FlutterPluginRegistrar> *)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
|
|
@ -0,0 +1 @@
|
|||
../../darwin/Classes/AudioServicePlugin.m
|
Loading…
Reference in New Issue