1 /*****************************************************************************
2 * VLCInputManager.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2015 VLC authors and VideoLAN
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20 *****************************************************************************/
22 #import "VLCInputManager.h"
24 #import "VLCCoreInteraction.h"
25 #import "CompatibilityFixes.h"
26 #import "VLCExtensionsManager.h"
28 #import "VLCMainMenu.h"
29 #import "VLCMainWindow.h"
30 #import "VLCPlaylist.h"
31 #import "VLCPlaylistInfo.h"
32 #import "VLCResumeDialogController.h"
33 #import "VLCTrackSynchronizationWindowController.h"
34 #import "VLCVoutView.h"
39 #pragma mark Callbacks
41 static int InputThreadChanged(vlc_object_t *p_this, const char *psz_var,
42 vlc_value_t oldval, vlc_value_t new_val, void *param)
45 VLCInputManager *inputManager = (__bridge VLCInputManager *)param;
46 [inputManager performSelectorOnMainThread:@selector(inputThreadChanged) withObject:nil waitUntilDone:NO];
53 static int InputEvent(vlc_object_t *p_this, const char *psz_var,
54 vlc_value_t oldval, vlc_value_t new_val, void *param)
57 VLCInputManager *inputManager = (__bridge VLCInputManager *)param;
59 switch (new_val.i_int) {
60 case INPUT_EVENT_STATE:
61 [inputManager performSelectorOnMainThread:@selector(playbackStatusUpdated) withObject: nil waitUntilDone:NO];
63 case INPUT_EVENT_RATE:
64 [[[VLCMain sharedInstance] mainMenu] performSelectorOnMainThread:@selector(updatePlaybackRate) withObject: nil waitUntilDone:NO];
66 case INPUT_EVENT_POSITION:
67 [[[VLCMain sharedInstance] mainWindow] performSelectorOnMainThread:@selector(updateTimeSlider) withObject: nil waitUntilDone:NO];
68 [[[VLCMain sharedInstance] statusBarIcon] performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];
70 case INPUT_EVENT_TITLE:
71 case INPUT_EVENT_CHAPTER:
72 [inputManager performSelectorOnMainThread:@selector(updateMainMenu) withObject: nil waitUntilDone:NO];
74 case INPUT_EVENT_CACHE:
75 [inputManager performSelectorOnMainThread:@selector(updateMainWindow) withObject:nil waitUntilDone:NO];
77 case INPUT_EVENT_STATISTICS:
78 dispatch_async(dispatch_get_main_queue(), ^{
79 [[[VLCMain sharedInstance] currentMediaInfoPanel] updateStatistics];
84 case INPUT_EVENT_TELETEXT:
86 case INPUT_EVENT_AOUT:
88 case INPUT_EVENT_VOUT:
90 case INPUT_EVENT_ITEM_META:
91 case INPUT_EVENT_ITEM_INFO:
92 [inputManager performSelectorOnMainThread:@selector(updateMainMenu) withObject: nil waitUntilDone:NO];
93 [inputManager performSelectorOnMainThread:@selector(updateName) withObject: nil waitUntilDone:NO];
94 [inputManager performSelectorOnMainThread:@selector(updateMetaAndInfo) withObject: nil waitUntilDone:NO];
96 case INPUT_EVENT_BOOKMARK:
98 case INPUT_EVENT_RECORD:
99 dispatch_async(dispatch_get_main_queue(), ^{
100 [[[VLCMain sharedInstance] mainMenu] updateRecordState: var_InheritBool(p_this, "record")];
103 case INPUT_EVENT_PROGRAM:
104 [inputManager performSelectorOnMainThread:@selector(updateMainMenu) withObject: nil waitUntilDone:NO];
106 case INPUT_EVENT_ITEM_EPG:
108 case INPUT_EVENT_SIGNAL:
111 case INPUT_EVENT_ITEM_NAME:
112 [inputManager performSelectorOnMainThread:@selector(updateName) withObject: nil waitUntilDone:NO];
115 case INPUT_EVENT_AUDIO_DELAY:
116 case INPUT_EVENT_SUBTITLE_DELAY:
117 [inputManager performSelectorOnMainThread:@selector(updateDelays) withObject:nil waitUntilDone:NO];
120 case INPUT_EVENT_DEAD:
121 [inputManager performSelectorOnMainThread:@selector(updateName) withObject: nil waitUntilDone:NO];
122 [[[VLCMain sharedInstance] mainWindow] performSelectorOnMainThread:@selector(updateTimeSlider) withObject:nil waitUntilDone:NO];
134 #pragma mark InputManager implementation
136 @interface VLCInputManager()
138 __weak VLCMain *o_main;
140 input_thread_t *p_current_input;
141 dispatch_queue_t informInputChangedQueue;
143 /* sleep management */
144 IOPMAssertionID systemSleepAssertionID;
145 IOPMAssertionID userActivityAssertionID;
147 /* iTunes/Spotify play/pause support */
148 BOOL b_has_itunes_paused;
149 BOOL b_has_spotify_paused;
150 NSTimer *o_itunes_play_timer;
154 @implementation VLCInputManager
156 - (id)initWithMain:(VLCMain *)o_mainObj
160 msg_Dbg(getIntf(), "Initializing input manager");
163 var_AddCallback(pl_Get(getIntf()), "input-current", InputThreadChanged, (__bridge void *)self);
165 informInputChangedQueue = dispatch_queue_create("org.videolan.vlc.inputChangedQueue", DISPATCH_QUEUE_SERIAL);
173 msg_Dbg(getIntf(), "Deinitializing input manager");
174 if (p_current_input) {
175 /* continue playback where you left off */
176 [[o_main playlist] storePlaybackPositionForItem:p_current_input];
178 var_DelCallback(p_current_input, "intf-event", InputEvent, (__bridge void *)self);
179 vlc_object_release(p_current_input);
180 p_current_input = NULL;
183 var_DelCallback(pl_Get(getIntf()), "input-current", InputThreadChanged, (__bridge void *)self);
185 #if !OS_OBJECT_USE_OBJC
186 dispatch_release(informInputChangedQueue);
190 - (void)inputThreadChanged
192 if (p_current_input) {
193 var_DelCallback(p_current_input, "intf-event", InputEvent, (__bridge void *)self);
194 vlc_object_release(p_current_input);
195 p_current_input = NULL;
197 [[o_main mainMenu] setRateControlsEnabled: NO];
199 [[NSNotificationCenter defaultCenter] postNotificationName:VLCInputChangedNotification
203 // Cancel pending resume dialogs
204 [[[VLCMain sharedInstance] resumeDialog] cancel];
206 input_thread_t *p_input_changed = NULL;
208 // object is hold here and released then it is dead
209 p_current_input = playlist_CurrentInput(pl_Get(getIntf()));
210 if (p_current_input) {
211 var_AddCallback(p_current_input, "intf-event", InputEvent, (__bridge void *)self);
212 [self playbackStatusUpdated];
213 [[o_main mainMenu] setRateControlsEnabled: YES];
215 if ([o_main activeVideoPlayback] && [[[o_main mainWindow] videoView] isHidden]) {
216 [[o_main mainWindow] changePlaylistState: psPlaylistItemChangedEvent];
219 p_input_changed = vlc_object_hold(p_current_input);
221 [[o_main playlist] currentlyPlayingItemChanged];
223 [[o_main playlist] continuePlaybackWhereYouLeftOff:p_current_input];
225 [[NSNotificationCenter defaultCenter] postNotificationName:VLCInputChangedNotification
229 [self updateMetaAndInfo];
231 [self updateMainWindow];
233 [self updateMainMenu];
236 * Due to constraints within NSAttributedString's main loop runtime handling
237 * and other issues, we need to inform the extension manager on a separate thread.
238 * The serial queue ensures that changed inputs are propagated in the same order as they arrive.
240 dispatch_async(informInputChangedQueue, ^{
241 [[o_main extensionsManager] inputChanged:p_input_changed];
243 vlc_object_release(p_input_changed);
247 - (void)playbackStatusUpdated
249 intf_thread_t *p_intf = getIntf();
251 if (p_current_input) {
252 state = var_GetInteger(p_current_input, "state");
255 int i_control_itunes = var_InheritInteger(p_intf, "macosx-control-itunes");
256 // cancel itunes timer if next item starts playing
257 if (state > -1 && state != END_S && i_control_itunes > 0) {
258 if (o_itunes_play_timer) {
259 [o_itunes_play_timer invalidate];
260 o_itunes_play_timer = nil;
264 if (state == PLAYING_S) {
265 if (i_control_itunes > 0) {
267 if (!b_has_itunes_paused) {
268 iTunesApplication *iTunesApp = (iTunesApplication *) [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
269 if (iTunesApp && [iTunesApp isRunning]) {
270 if ([iTunesApp playerState] == iTunesEPlSPlaying) {
271 msg_Dbg(p_intf, "pausing iTunes");
273 b_has_itunes_paused = YES;
279 if (!b_has_spotify_paused) {
280 SpotifyApplication *spotifyApp = (SpotifyApplication *) [SBApplication applicationWithBundleIdentifier:@"com.spotify.client"];
283 if ([spotifyApp respondsToSelector:@selector(isRunning)] && [spotifyApp respondsToSelector:@selector(playerState)]) {
284 if ([spotifyApp isRunning] && [spotifyApp playerState] == kSpotifyPlayerStatePlaying) {
285 msg_Dbg(p_intf, "pausing Spotify");
287 b_has_spotify_paused = YES;
294 BOOL shouldDisableScreensaver = var_InheritBool(p_intf, "disable-screensaver");
296 /* Declare user activity.
297 This wakes the display if it is off, and postpones display sleep according to the users system preferences
298 Available from 10.7.3 */
299 if ([o_main activeVideoPlayback] && &IOPMAssertionDeclareUserActivity && shouldDisableScreensaver)
301 CFStringRef reasonForActivity = CFStringCreateWithCString(kCFAllocatorDefault, _("VLC media playback"), kCFStringEncodingUTF8);
302 IOReturn success = IOPMAssertionDeclareUserActivity(reasonForActivity,
303 kIOPMUserActiveLocal,
304 &userActivityAssertionID);
305 CFRelease(reasonForActivity);
307 if (success != kIOReturnSuccess)
308 msg_Warn(getIntf(), "failed to declare user activity");
312 /* prevent the system from sleeping */
313 if (systemSleepAssertionID > 0) {
314 msg_Dbg(getIntf(), "releasing old sleep blocker (%i)" , systemSleepAssertionID);
315 IOPMAssertionRelease(systemSleepAssertionID);
319 /* work-around a bug in 10.7.4 and 10.7.5, so check for 10.7.x < 10.7.4 and 10.8 */
320 if (NSAppKitVersionNumber < 1115.2) {
321 /* fall-back on the 10.5 mode, which also works on 10.7.4 and 10.7.5 */
322 if ([o_main activeVideoPlayback] && shouldDisableScreensaver)
323 success = IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &systemSleepAssertionID);
325 success = IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &systemSleepAssertionID);
327 CFStringRef reasonForActivity = CFStringCreateWithCString(kCFAllocatorDefault, _("VLC media playback"), kCFStringEncodingUTF8);
328 if ([o_main activeVideoPlayback] && shouldDisableScreensaver)
329 success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, reasonForActivity, &systemSleepAssertionID);
331 success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, reasonForActivity, &systemSleepAssertionID);
332 CFRelease(reasonForActivity);
335 if (success == kIOReturnSuccess)
336 msg_Dbg(getIntf(), "prevented sleep through IOKit (%i)", systemSleepAssertionID);
338 msg_Warn(getIntf(), "failed to prevent system sleep through IOKit");
340 [[o_main mainMenu] setPause];
341 [[o_main mainWindow] setPause];
343 [[o_main mainMenu] setSubmenusEnabled: FALSE];
344 [[o_main mainMenu] setPlay];
345 [[o_main mainWindow] setPlay];
347 /* allow the system to sleep again */
348 if (systemSleepAssertionID > 0) {
349 msg_Dbg(getIntf(), "releasing sleep blocker (%i)" , systemSleepAssertionID);
350 IOPMAssertionRelease(systemSleepAssertionID);
353 if (state == END_S || state == -1) {
354 /* continue playback where you left off */
356 [[o_main playlist] storePlaybackPositionForItem:p_current_input];
358 if (i_control_itunes > 0) {
359 if (o_itunes_play_timer) {
360 [o_itunes_play_timer invalidate];
362 o_itunes_play_timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
364 selector: @selector(resumeItunesPlayback:)
371 [self updateMainWindow];
372 [self sendDistributedNotificationWithUpdatedPlaybackStatus];
376 - (void)resumeItunesPlayback:(id)sender
378 intf_thread_t *p_intf = getIntf();
379 if (var_InheritInteger(p_intf, "macosx-control-itunes") > 1) {
380 if (b_has_itunes_paused) {
381 iTunesApplication *iTunesApp = (iTunesApplication *) [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
382 if (iTunesApp && [iTunesApp isRunning]) {
383 if ([iTunesApp playerState] == iTunesEPlSPaused) {
384 msg_Dbg(p_intf, "unpausing iTunes");
385 [iTunesApp playpause];
390 if (b_has_spotify_paused) {
391 SpotifyApplication *spotifyApp = (SpotifyApplication *) [SBApplication applicationWithBundleIdentifier:@"com.spotify.client"];
393 if ([spotifyApp respondsToSelector:@selector(isRunning)] && [spotifyApp respondsToSelector:@selector(playerState)]) {
394 if ([spotifyApp isRunning] && [spotifyApp playerState] == kSpotifyPlayerStatePaused) {
395 msg_Dbg(p_intf, "unpausing Spotify");
403 b_has_itunes_paused = NO;
404 b_has_spotify_paused = NO;
405 o_itunes_play_timer = nil;
408 - (void)updateMetaAndInfo
410 if (!p_current_input) {
411 [[[VLCMain sharedInstance] currentMediaInfoPanel] updatePanelWithItem:nil];
415 input_item_t *p_input_item = input_GetItem(p_current_input);
417 [[[o_main playlist] model] updateItem:p_input_item];
418 [[[VLCMain sharedInstance] currentMediaInfoPanel] updatePanelWithItem:p_input_item];
421 - (void)updateMainWindow
423 [[o_main mainWindow] updateWindow];
428 [[o_main mainWindow] updateName];
433 [[[VLCMain sharedInstance] trackSyncPanel] updateValues];
436 - (void)updateMainMenu
438 [[o_main mainMenu] setupMenus];
439 [[o_main mainMenu] updatePlaybackRate];
440 [[VLCCoreInteraction sharedInstance] resetAtoB];
443 - (void)sendDistributedNotificationWithUpdatedPlaybackStatus
445 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"VLCPlayerStateDidChange"
448 deliverImmediately:YES];
453 return p_current_input != NULL;