diff --git a/README.md b/README.md index 68a233e..0f5eea3 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ MusicControls.destroy(onSuccess, onError); - Subscribe events to the media controller: ```javascript function events(action) { - switch(action) { + + const message = JSON.parse(action).message; + switch(message) { case 'music-controls-next': // Do something break; @@ -69,6 +71,14 @@ function events(action) { case 'music-controls-toggle-play-pause' : // Do something break; + case 'music-controls-seek-to': + const seekToInSeconds = JSON.parse(action).position; + MusicControls.updateElapsed({ + elapsed: seekToInSeconds, + isPlaying: true //optional + }); + // Do something + break; // Headset events (Android only) // All media button events are listed below @@ -99,6 +109,16 @@ MusicControls.listen(); MusicControls.updateIsPlaying(true); // toggle the play/pause notification button MusicControls.updateDismissable(true); ``` + +- iOS Specific Events: +Allows you to listen for iOS events fired from the scrubber in control center. +```javascript +MusicControls.updateElapsed({ + elapsed: 208, // seconds + isPlaying: true // optional argument. +}); +``` + - List of media button events (Android only): ```javascript 'music-controls-media-button-next', 'music-controls-media-button-pause', 'music-controls-media-button-play', diff --git a/src/android/MusicControlsBroadcastReceiver.java b/src/android/MusicControlsBroadcastReceiver.java index dd14247..1d3598b 100644 --- a/src/android/MusicControlsBroadcastReceiver.java +++ b/src/android/MusicControlsBroadcastReceiver.java @@ -26,7 +26,7 @@ public void setCallback(CallbackContext cb){ public void stopListening(){ if (this.cb != null){ - this.cb.success("music-controls-stop-listening"); + this.cb.success("{\"message\": \"music-controls-stop-listening\" }"); this.cb = null; } } @@ -42,12 +42,12 @@ public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra("state", -1); switch (state) { case 0: - this.cb.success("music-controls-headset-unplugged"); + this.cb.success("{\"message\": \"music-controls-headset-unplugged\"}"); this.cb = null; this.musicControls.unregisterMediaButtonEvent(); break; case 1: - this.cb.success("music-controls-headset-plugged"); + this.cb.success("{\"message\": \"music-controls-headset-plugged\"}"); this.cb = null; this.musicControls.registerMediaButtonEvent(); break; @@ -62,75 +62,75 @@ public void onReceive(Context context, Intent intent) { int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_NEXT: - this.cb.success("music-controls-media-button-next"); + this.cb.success("{\"message\": \"music-controls-media-button-next\"}"); break; case KeyEvent.KEYCODE_MEDIA_PAUSE: - this.cb.success("music-controls-media-button-pause"); + this.cb.success("{\"message\": \"music-controls-media-button-pause\"}"); break; case KeyEvent.KEYCODE_MEDIA_PLAY: - this.cb.success("music-controls-media-button-play"); + this.cb.success("{\"message\": \"music-controls-media-button-play\"}"); break; case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - this.cb.success("music-controls-media-button-play-pause"); + this.cb.success("{\"message\": \"music-controls-media-button-play-pause\"}"); break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - this.cb.success("music-controls-media-button-previous"); + this.cb.success("{\"message\": \"music-controls-media-button-previous\"}"); break; case KeyEvent.KEYCODE_MEDIA_STOP: - this.cb.success("music-controls-media-button-stop"); + this.cb.success("{\"message\": \"music-controls-media-button-stop\"}"); break; case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - this.cb.success("music-controls-media-button-fast-forward"); + this.cb.success("{\"message\": \"music-controls-media-button-fast-forward\"}"); break; case KeyEvent.KEYCODE_MEDIA_REWIND: - this.cb.success("music-controls-media-button-rewind"); + this.cb.success("{\"message\": \"music-controls-media-button-rewind\"}"); break; case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: - this.cb.success("music-controls-media-button-skip-backward"); + this.cb.success("{\"message\": \"music-controls-media-button-skip-backward\"}"); break; case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: - this.cb.success("music-controls-media-button-skip-forward"); + this.cb.success("{\"message\": \"music-controls-media-button-skip-forward\"}"); break; case KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD: - this.cb.success("music-controls-media-button-step-backward"); + this.cb.success("{\"message\": \"music-controls-media-button-step-backward\"}"); break; case KeyEvent.KEYCODE_MEDIA_STEP_FORWARD: - this.cb.success("music-controls-media-button-step-forward"); + this.cb.success("{\"message\": \"music-controls-media-button-step-forward\"}"); break; case KeyEvent.KEYCODE_META_LEFT: - this.cb.success("music-controls-media-button-meta-left"); + this.cb.success("{\"message\": \"music-controls-media-button-meta-left\"}"); break; case KeyEvent.KEYCODE_META_RIGHT: - this.cb.success("music-controls-media-button-meta-right"); + this.cb.success("{\"message\": \"music-controls-media-button-meta-right\"}"); break; case KeyEvent.KEYCODE_MUSIC: - this.cb.success("music-controls-media-button-music"); + this.cb.success("{\"message\": \"music-controls-media-button-music\"}"); break; case KeyEvent.KEYCODE_VOLUME_UP: - this.cb.success("music-controls-media-button-volume-up"); + this.cb.success("{\"message\": \"music-controls-media-button-volume-up\"}"); break; case KeyEvent.KEYCODE_VOLUME_DOWN: - this.cb.success("music-controls-media-button-volume-down"); + this.cb.success("{\"message\": \"music-controls-media-button-volume-down\"}"); break; case KeyEvent.KEYCODE_VOLUME_MUTE: - this.cb.success("music-controls-media-button-volume-mute"); + this.cb.success("{\"message\": \"music-controls-media-button-volume-mute\"}"); break; case KeyEvent.KEYCODE_HEADSETHOOK: - this.cb.success("music-controls-media-button-headset-hook"); + this.cb.success("{\"message\": \"music-controls-media-button-headset-hook\"}"); break; default: - this.cb.success(message); + this.cb.success("{\"message\": \"" + message + "\"}"); break; } this.cb = null; } } else if (message.equals("music-controls-destroy")){ // Close Button - this.cb.success("music-controls-destroy"); + this.cb.success("{\"message\": \"music-controls-destroy\"}"); this.cb = null; this.musicControls.destroyPlayerNotification(); } else { - this.cb.success(message); + this.cb.success("{\"message\": \"" + message + "\"}"); this.cb = null; } diff --git a/src/ios/MusicControls.h b/src/ios/MusicControls.h index 55b8d90..3c92bce 100644 --- a/src/ios/MusicControls.h +++ b/src/ios/MusicControls.h @@ -19,6 +19,7 @@ - (void) create: (CDVInvokedUrlCommand *) command; - (void) updateIsPlaying: (CDVInvokedUrlCommand *) command; +- (void) updateElapsed: (CDVInvokedUrlCommand *) command; - (void) destroy: (CDVInvokedUrlCommand *) command; - (void) watch: (CDVInvokedUrlCommand *) command; - (MPMediaItemArtwork *) createCoverArtwork: (NSString *) coverUri; diff --git a/src/ios/MusicControls.m b/src/ios/MusicControls.m index 5279e9d..05fa05f 100644 --- a/src/ios/MusicControls.m +++ b/src/ios/MusicControls.m @@ -1,6 +1,6 @@ // // MusicControls.m -// +// // // Created by Juan Gonzalez on 12/16/16. // @@ -14,32 +14,32 @@ @implementation MusicControls - (void) create: (CDVInvokedUrlCommand *) command { NSDictionary * musicControlsInfoDict = [command.arguments objectAtIndex:0]; MusicControlsInfo * musicControlsInfo = [[MusicControlsInfo alloc] initWithDictionary:musicControlsInfoDict]; - + if (!NSClassFromString(@"MPNowPlayingInfoCenter")) { return; } - + [self.commandDelegate runInBackground:^{ MPNowPlayingInfoCenter * nowPlayingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; NSDictionary * nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo; NSMutableDictionary * updatedNowPlayingInfo = [NSMutableDictionary dictionaryWithDictionary:nowPlayingInfo]; - + MPMediaItemArtwork * mediaItemArtwork = [self createCoverArtwork:[musicControlsInfo cover]]; NSNumber * duration = [NSNumber numberWithInt:[musicControlsInfo duration]]; NSNumber * elapsed = [NSNumber numberWithInt:[musicControlsInfo elapsed]]; NSNumber * playbackRate = [NSNumber numberWithBool:[musicControlsInfo isPlaying]]; - + if (mediaItemArtwork != nil) { [updatedNowPlayingInfo setObject:mediaItemArtwork forKey:MPMediaItemPropertyArtwork]; } - + [updatedNowPlayingInfo setObject:[musicControlsInfo artist] forKey:MPMediaItemPropertyArtist]; [updatedNowPlayingInfo setObject:[musicControlsInfo track] forKey:MPMediaItemPropertyTitle]; [updatedNowPlayingInfo setObject:[musicControlsInfo album] forKey:MPMediaItemPropertyAlbumTitle]; [updatedNowPlayingInfo setObject:duration forKey:MPMediaItemPropertyPlaybackDuration]; [updatedNowPlayingInfo setObject:elapsed forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; [updatedNowPlayingInfo setObject:playbackRate forKey:MPNowPlayingInfoPropertyPlaybackRate]; - + nowPlayingInfoCenter.nowPlayingInfo = updatedNowPlayingInfo; }]; } @@ -48,18 +48,34 @@ - (void) updateIsPlaying: (CDVInvokedUrlCommand *) command { NSDictionary * musicControlsInfoDict = [command.arguments objectAtIndex:0]; MusicControlsInfo * musicControlsInfo = [[MusicControlsInfo alloc] initWithDictionary:musicControlsInfoDict]; NSNumber * playbackRate = [NSNumber numberWithBool:[musicControlsInfo isPlaying]]; - + if (!NSClassFromString(@"MPNowPlayingInfoCenter")) { return; } MPNowPlayingInfoCenter * nowPlayingCenter = [MPNowPlayingInfoCenter defaultCenter]; NSMutableDictionary * updatedNowPlayingInfo = [NSMutableDictionary dictionaryWithDictionary:nowPlayingCenter.nowPlayingInfo]; - + [updatedNowPlayingInfo setObject:playbackRate forKey:MPNowPlayingInfoPropertyPlaybackRate]; nowPlayingCenter.nowPlayingInfo = updatedNowPlayingInfo; } +- (void) updateElapsed: (CDVInvokedUrlCommand *) command { + NSDictionary * musicControlsInfoDict = [command.arguments objectAtIndex:0]; + MusicControlsInfo * musicControlsInfo = [[MusicControlsInfo alloc] initWithDictionary:musicControlsInfoDict]; + NSNumber * elapsed = [NSNumber numberWithDouble:[musicControlsInfo elapsed]]; + + if (!NSClassFromString(@"MPNowPlayingInfoCenter")) { + return; + } + + MPNowPlayingInfoCenter * nowPlayingCenter = [MPNowPlayingInfoCenter defaultCenter]; + NSMutableDictionary * updatedNowPlayingInfo = [NSMutableDictionary dictionaryWithDictionary:nowPlayingCenter.nowPlayingInfo]; + + [updatedNowPlayingInfo setObject:elapsed forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; + nowPlayingCenter.nowPlayingInfo = updatedNowPlayingInfo; +} + - (void) destroy: (CDVInvokedUrlCommand *) command { [self deregisterMusicControlsEventListener]; } @@ -71,20 +87,20 @@ - (void) watch: (CDVInvokedUrlCommand *) command { - (MPMediaItemArtwork *) createCoverArtwork: (NSString *) coverUri { UIImage * coverImage = nil; - + if (coverUri == nil) { return nil; } - + if ([coverUri hasPrefix:@"http://"] || [coverUri hasPrefix:@"https://"]) { NSURL * coverImageUrl = [NSURL URLWithString:coverUri]; NSData * coverImageData = [NSData dataWithContentsOfURL: coverImageUrl]; - + coverImage = [UIImage imageWithData: coverImageData]; } else if ([coverUri hasPrefix:@"file://"]) { NSString * fullCoverImagePath = [coverUri stringByReplacingOccurrencesOfString:@"file://" withString:@""]; - + if ([[NSFileManager defaultManager] fileExistsAtPath: fullCoverImagePath]) { coverImage = [[UIImage alloc] initWithContentsOfFile: fullCoverImagePath]; } @@ -92,7 +108,7 @@ - (MPMediaItemArtwork *) createCoverArtwork: (NSString *) coverUri { else if (![coverUri isEqual:@""]) { NSString * baseCoverImagePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString * fullCoverImagePath = [NSString stringWithFormat:@"%@%@", baseCoverImagePath, coverUri]; - + if ([[NSFileManager defaultManager] fileExistsAtPath:fullCoverImagePath]) { coverImage = [UIImage imageNamed:fullCoverImagePath]; } @@ -100,7 +116,7 @@ - (MPMediaItemArtwork *) createCoverArtwork: (NSString *) coverUri { else { coverImage = [UIImage imageNamed:@"none"]; } - + return [self isCoverImageValid:coverImage] ? [[MPMediaItemArtwork alloc] initWithImage:coverImage] : nil; } @@ -110,44 +126,45 @@ - (bool) isCoverImageValid: (UIImage *) coverImage { - (void) handleMusicControlsNotification: (NSNotification *) notification { UIEvent * receivedEvent = notification.object; - + if ([self latestEventCallbackId] == nil) { return; } - + if (receivedEvent.type == UIEventTypeRemoteControl) { NSString * action; - + switch (receivedEvent.subtype) { case UIEventSubtypeRemoteControlTogglePlayPause: action = @"music-controls-toggle-play-pause"; break; - + case UIEventSubtypeRemoteControlPlay: action = @"music-controls-play"; break; - + case UIEventSubtypeRemoteControlPause: action = @"music-controls-pause"; break; - + case UIEventSubtypeRemoteControlPreviousTrack: action = @"music-controls-previous"; break; - + case UIEventSubtypeRemoteControlNextTrack: action = @"music-controls-next"; break; - + case UIEventSubtypeRemoteControlStop: action = @"music-controls-destroy"; break; - + default: break; } - - CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:action]; + + NSString * jsonAction = [NSString stringWithFormat:@"{\"message\":\"%@\"}", action]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:jsonAction]; [self.commandDelegate sendPluginResult:pluginResult callbackId:[self latestEventCallbackId]]; } } @@ -155,12 +172,33 @@ - (void) handleMusicControlsNotification: (NSNotification *) notification { - (void) registerMusicControlsEventListener { [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMusicControlsNotification:) name:@"musicControlsEventNotification" object:nil]; + + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_0) { + //only available in iOS 9.1 and up. + MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; + [commandCenter.changePlaybackPositionCommand setEnabled:true]; + [commandCenter.changePlaybackPositionCommand addTarget:self action:@selector(changedThumbSliderOnLockScreen:)]; + } +} + +- (MPRemoteCommandHandlerStatus)changedThumbSliderOnLockScreen:(MPChangePlaybackPositionCommandEvent *)event { + NSString * seekTo = [NSString stringWithFormat:@"{\"message\":\"music-controls-seek-to\",\"position\":\"%f\"}", event.positionTime]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:seekTo]; + pluginResult.associatedObject = @{@"position":[NSNumber numberWithDouble: event.positionTime]}; + [self.commandDelegate sendPluginResult:pluginResult callbackId:[self latestEventCallbackId]]; + return MPRemoteCommandHandlerStatusSuccess; } - (void) deregisterMusicControlsEventListener { [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"receivedEvent" object:nil]; [self setLatestEventCallbackId:nil]; + + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_0) { + MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; + [commandCenter.changePlaybackPositionCommand setEnabled:false]; + [commandCenter.changePlaybackPositionCommand removeTarget:self action:NULL]; + } } - (void) dealloc { diff --git a/src/windows/MusicControlsProxy.js b/src/windows/MusicControlsProxy.js index eadbe93..35042f5 100644 --- a/src/windows/MusicControlsProxy.js +++ b/src/windows/MusicControlsProxy.js @@ -8,19 +8,19 @@ var onKey = function (event) { var Button = Windows.Media.SystemMediaTransportControlsButton; switch (event.button) { case Button.play: - onUpdate('music-controls-play'); + onUpdate("{\"message\": \"music-controls-play\"}"); break; case Button.pause: - onUpdate('music-controls-pause'); + onUpdate("{\"message\": \"music-controls-pause\"}"); break; case Button.stop: - onUpdate('music-controls-stop'); + onUpdate("{\"message\": \"music-controls-stop\"}"); break; case Button.next: - onUpdate('music-controls-next'); + onUpdate("{\"message\": \"music-controls-next\"}"); break; case Button.previous: - onUpdate('music-controls-previous'); + onUpdate("{\"message\": \"music-controls-previous\"}"); break; } }; @@ -47,7 +47,7 @@ cordova.commandProxy.add("MusicControls",{ mc.playbackStatus = Windows.Media.MediaPlaybackStatus.playing; else mc.playbackStatus = Windows.Media.MediaPlaybackStatus.stopped; - + if (!/^(f|ht)tps?:\/\//i.test(data.cover)) { var cover = new Windows.Foundation.Uri("ms-appdata://" + data.cover); diff --git a/www/MusicControls.js b/www/MusicControls.js index b548504..2980cfa 100644 --- a/www/MusicControls.js +++ b/www/MusicControls.js @@ -21,6 +21,12 @@ module.exports = { updateIsPlaying: function (isPlaying, successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, 'MusicControls', 'updateIsPlaying', [{isPlaying: isPlaying}]); }, + updateElapsed: function (args, successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, 'MusicControls', 'updateElapsed', [{ + elapsed: args.elapsed, + isPlaying: (args.isPlaying === undefined) ? '' : !!args.isPlaying + }]); + }, updateDismissable: function (dismissable, successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, 'MusicControls', 'updateDismissable', [{dismissable: dismissable}]); },