VLCWatchCommunication.m 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*****************************************************************************
  2. * VLCWatchCommunication.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Author: Tobias Conradi <videolan # tobias-conradi.de>
  9. *
  10. * Refer to the COPYING file of the official project for license.
  11. *****************************************************************************/
  12. #import "VLCWatchCommunication.h"
  13. #import "VLCWatchMessage.h"
  14. #import "VLCPlaybackController+MediaLibrary.h"
  15. #import <MediaPlayer/MediaPlayer.h>
  16. @implementation VLCWatchCommunication
  17. + (BOOL)isSupported {
  18. return [WCSession class] != nil && [WCSession isSupported];
  19. }
  20. - (instancetype)init
  21. {
  22. self = [super init];
  23. if (self) {
  24. if ([WCSession isSupported]) {
  25. WCSession *session = [WCSession defaultSession];
  26. session.delegate = self;
  27. [session activateSession];
  28. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(savedManagedObjectContextNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
  29. }
  30. }
  31. return self;
  32. }
  33. - (void)dealloc {
  34. [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
  35. }
  36. static VLCWatchCommunication *_singeltonInstance = nil;
  37. + (VLCWatchCommunication *)sharedInstance
  38. {
  39. @synchronized(self) {
  40. static dispatch_once_t pred;
  41. dispatch_once(&pred, ^{
  42. _singeltonInstance = [[self alloc] init];
  43. });
  44. }
  45. return _singeltonInstance;
  46. }
  47. - (void)playFileFromWatch:(VLCWatchMessage *)message
  48. {
  49. NSManagedObject *managedObject = nil;
  50. NSString *uriString = (id)message.payload;
  51. if ([uriString isKindOfClass:[NSString class]]) {
  52. NSURL *uriRepresentation = [NSURL URLWithString:uriString];
  53. managedObject = [[MLMediaLibrary sharedMediaLibrary] objectForURIRepresentation:uriRepresentation];
  54. }
  55. if (managedObject == nil) {
  56. APLog(@"%s file not found: %@",__PRETTY_FUNCTION__,message);
  57. return;
  58. }
  59. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  60. [vpc playMediaLibraryObject:managedObject];
  61. }
  62. - (NSDictionary *)handleMessage:(nonnull VLCWatchMessage *)message {
  63. UIApplication *application = [UIApplication sharedApplication];
  64. /* dispatch background task */
  65. __block UIBackgroundTaskIdentifier taskIdentifier = [application beginBackgroundTaskWithName:nil
  66. expirationHandler:^{
  67. [application endBackgroundTask:taskIdentifier];
  68. taskIdentifier = UIBackgroundTaskInvalid;
  69. }];
  70. NSString *name = message.name;
  71. NSDictionary *responseDict = @{};
  72. if ([name isEqualToString:VLCWatchMessageNameGetNowPlayingInfo]) {
  73. responseDict = [self nowPlayingResponseDict];
  74. } else if ([name isEqualToString:VLCWatchMessageNamePlayPause]) {
  75. [[VLCPlaybackController sharedInstance] playPause];
  76. responseDict = @{@"playing": @([VLCPlaybackController sharedInstance].isPlaying)};
  77. } else if ([name isEqualToString:VLCWatchMessageNameSkipForward]) {
  78. [[VLCPlaybackController sharedInstance] forward];
  79. } else if ([name isEqualToString:VLCWatchMessageNameSkipBackward]) {
  80. [[VLCPlaybackController sharedInstance] backward];
  81. } else if ([name isEqualToString:VLCWatchMessageNamePlayFile]) {
  82. [self playFileFromWatch:message];
  83. } else if ([name isEqualToString:VLCWatchMessageNameSetVolume]) {
  84. [self setVolumeFromWatch:message];
  85. } else {
  86. APLog(@"Did not handle request from WatchKit Extension: %@",message);
  87. }
  88. return responseDict;
  89. }
  90. - (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)userInfo replyHandler:(nonnull void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {
  91. VLCWatchMessage *message = [[VLCWatchMessage alloc] initWithDictionary:userInfo];
  92. NSDictionary *responseDict = [self handleMessage:message];
  93. replyHandler(responseDict);
  94. }
  95. - (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)messageDict {
  96. VLCWatchMessage *message = [[VLCWatchMessage alloc] initWithDictionary:messageDict];
  97. [self handleMessage:message];
  98. }
  99. - (void)setVolumeFromWatch:(VLCWatchMessage *)message
  100. {
  101. NSNumber *volume = (id)message.payload;
  102. if ([volume isKindOfClass:[NSNumber class]]) {
  103. /*
  104. * Since WatchKit doesn't provide something like MPVolumeView we use deprecated API.
  105. * rdar://20783803 Feature Request: WatchKit equivalent for MPVolumeView
  106. */
  107. [MPMusicPlayerController applicationMusicPlayer].volume = volume.floatValue;
  108. }
  109. }
  110. - (NSDictionary *)nowPlayingResponseDict {
  111. NSMutableDictionary *response = [NSMutableDictionary new];
  112. NSMutableDictionary *nowPlayingInfo = [[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo mutableCopy];
  113. NSNumber *playbackTime = [VLCPlaybackController sharedInstance].mediaPlayer.time.numberValue;
  114. if (playbackTime) {
  115. nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(playbackTime.floatValue/1000);
  116. }
  117. if (nowPlayingInfo) {
  118. response[@"nowPlayingInfo"] = nowPlayingInfo;
  119. }
  120. MLFile *currentFile = [VLCPlaybackController sharedInstance].currentlyPlayingMediaFile;
  121. NSString *URIString = currentFile.objectID.URIRepresentation.absoluteString;
  122. if (URIString) {
  123. response[@"URIRepresentation"] = URIString;
  124. }
  125. response[@"volume"] = @([MPMusicPlayerController applicationMusicPlayer].volume);
  126. return response;
  127. }
  128. #pragma mark - Notifications
  129. - (void)startRelayingNotificationName:(nullable NSString *)name object:(nullable id)object {
  130. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(relayNotification:) name:name object:object];
  131. }
  132. - (void)stopRelayingNotificationName:(nullable NSString *)name object:(nullable id)object {
  133. [[NSNotificationCenter defaultCenter] removeObserver:self name:name object:object];
  134. }
  135. - (void)relayNotification:(NSNotification *)notification {
  136. NSMutableDictionary *payload = [NSMutableDictionary dictionary];
  137. payload[@"name"] = notification.name;
  138. if (notification.userInfo) {
  139. payload[@"userInfo"] = notification.userInfo;
  140. }
  141. NSDictionary *dict = [VLCWatchMessage messageDictionaryForName:VLCWatchMessageNameNotification
  142. payload:payload];
  143. if ([WCSession isSupported] && [[WCSession defaultSession] isReachable]) {
  144. [[WCSession defaultSession] sendMessage:dict replyHandler:nil errorHandler:nil];
  145. }
  146. }
  147. #pragma mark - Copy CoreData to Watch
  148. - (void)savedManagedObjectContextNotification:(NSNotification *)notification {
  149. NSManagedObjectContext *moc = notification.object;
  150. if (moc.persistentStoreCoordinator == [[MLMediaLibrary sharedMediaLibrary] persistentStoreCoordinator]) {
  151. [self copyCoreDataToWatch];
  152. }
  153. }
  154. - (void)copyCoreDataToWatch {
  155. if (![[WCSession defaultSession] isReachable]) return;
  156. MLMediaLibrary *library = [MLMediaLibrary sharedMediaLibrary];
  157. NSPersistentStoreCoordinator *libraryPSC = [library persistentStoreCoordinator];
  158. NSPersistentStore *persistentStore = [libraryPSC persistentStoreForURL:[library persistentStoreURL]];
  159. NSURL *tmpURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:persistentStore.URL.lastPathComponent]];
  160. NSMutableDictionary *destOptions = [persistentStore.options mutableCopy] ?: [NSMutableDictionary new];
  161. destOptions[NSSQLitePragmasOption] = @{@"journal_mode": @"DELETE"};
  162. NSError *error;
  163. bool success = [libraryPSC replacePersistentStoreAtURL:tmpURL destinationOptions:destOptions withPersistentStoreFromURL:persistentStore.URL sourceOptions:persistentStore.options storeType:NSSQLiteStoreType error:&error];
  164. if (!success) {
  165. NSLog(@"%s failed to copy persistent store to tmp location for copy to watch with error %@",__PRETTY_FUNCTION__,error);
  166. }
  167. NSDictionary *metadata = @{@"filetype":@"coredata"};
  168. [[WCSession defaultSession] transferFile:tmpURL metadata:metadata];
  169. }
  170. @end