1 /*****************************************************************************
2 * VLCFSPanelController.m: macOS fullscreen controls window controller
3 *****************************************************************************
4 * Copyright (C) 2006-2016 VLC authors and VideoLAN
7 * Authors: Jérôme Decoodt <djc at videolan dot org>
8 * Felix Paul Kühne <fkuehne at videolan dot org>
9 * David Fuhrmann <david dot fuhrmann at googlemail dot com>
10 * Marvin Scholz <epirat07 at gmail dot com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 #import "VLCFSPanelController.h"
28 #import "VLCCoreInteraction.h"
31 @interface VLCFSPanelController () {
33 CGDirectDisplayID _displayID;
38 @implementation VLCFSPanelController
41 #pragma mark Initialization
45 self = [super initWithWindowNibName:@"VLCFullScreenPanel"];
51 [super windowDidLoad];
53 /* Do some window setup that is not possible in IB */
54 [self.window setOpaque:NO];
55 [self.window setAlphaValue:0.0f];
56 [self.window setMovableByWindowBackground:YES];
57 [self.window setLevel:NSModalPanelWindowLevel];
58 [self.window setStyleMask:self.window.styleMask | NSResizableWindowMask];
59 [self.window setBackgroundColor:[NSColor clearColor]];
61 /* Inject correct background view depending on OS support */
62 [self injectVisualEffectView];
63 //[self injectBackgroundView];
65 [(NSButtonCell*)[_playPauseButton cell] setHighlightsBy:NSPushInCellMask];
66 [(NSButtonCell*)[_playPauseButton cell] setShowsStateBy:NSContentsCellMask];
68 [_volumeSlider setMaxValue:[[VLCCoreInteraction sharedInstance] maxVolume]];
69 [_volumeSlider setIntValue:AOUT_VOLUME_DEFAULT];
73 #pragma mark Control Actions
75 - (IBAction)togglePlayPause:(id)sender
77 [[VLCCoreInteraction sharedInstance] playOrPause];
80 - (IBAction)jumpForward:(id)sender
82 static NSTimeInterval last_event = 0;
83 if (([NSDate timeIntervalSinceReferenceDate] - last_event) > 0.16) {
84 /* We just skipped 4 "continous" events, otherwise we are too fast */
85 [[VLCCoreInteraction sharedInstance] forwardExtraShort];
86 last_event = [NSDate timeIntervalSinceReferenceDate];
90 - (IBAction)jumpBackward:(id)sender
92 static NSTimeInterval last_event = 0;
93 if (([NSDate timeIntervalSinceReferenceDate] - last_event) > 0.16) {
94 /* We just skipped 4 "continous" events, otherwise we are too fast */
95 [[VLCCoreInteraction sharedInstance] backwardExtraShort];
96 last_event = [NSDate timeIntervalSinceReferenceDate];
100 - (IBAction)gotoPrevious:(id)sender
102 [[VLCCoreInteraction sharedInstance] previous];
105 - (IBAction)gotoNext:(id)sender
107 [[VLCCoreInteraction sharedInstance] next];
110 - (IBAction)toggleFullscreen:(id)sender
112 [[VLCCoreInteraction sharedInstance] toggleFullscreen];
115 - (IBAction)timeSliderUpdate:(id)sender
117 input_thread_t *p_input;
118 p_input = pl_CurrentInput(getIntf());
122 pos.f_float = [_timeSlider floatValue] / 10000.;
123 var_Set(p_input, "position", pos);
124 vlc_object_release(p_input);
126 [[[VLCMain sharedInstance] mainWindow] updateTimeSlider];
129 - (IBAction)volumeSliderUpdate:(id)sender
131 [[VLCCoreInteraction sharedInstance] setVolume:[sender intValue]];
135 #pragma mark Metadata and state updates
139 [_playPauseButton setState:NSOffState];
144 [_playPauseButton setState:NSOnState];
147 - (void)setStreamTitle:(NSString *)title
149 [_mediaTitle setStringValue:title];
152 - (void)updatePositionAndTime
154 input_thread_t *p_input = pl_CurrentInput(getIntf());
156 /* If nothing is playing, reset times and slider */
158 [_timeSlider setFloatValue:0.0];
159 [_elapsedTime setStringValue:@""];
160 [_remainingOrTotalTime setHidden:YES];
165 char psz_time[MSTRTIME_MAX_SIZE];
167 var_Get(p_input, "position", &pos);
168 float f_updated = 10000. * pos.f_float;
169 [_timeSlider setFloatValue:f_updated];
172 int64_t t = var_GetInteger(p_input, "time");
173 mtime_t dur = input_item_GetDuration(input_GetItem(p_input));
175 /* Update total duration (right field) */
177 [_remainingOrTotalTime setHidden:YES];
179 [_remainingOrTotalTime setHidden:NO];
183 if ([_remainingOrTotalTime timeRemaining]) {
184 mtime_t remaining = 0;
187 totalTime = [NSString stringWithFormat:@"-%s", secstotimestr(psz_time, (remaining / 1000000))];
189 totalTime = toNSStr(secstotimestr(psz_time, (dur / 1000000)));
191 [_remainingOrTotalTime setStringValue:totalTime];
194 /* Update current position (left field) */
195 NSString *playbackPosition = toNSStr(secstotimestr(psz_time, t / CLOCK_FREQ));
197 [_elapsedTime setStringValue:playbackPosition];
198 vlc_object_release(p_input);
201 - (void)setSeekable:(BOOL)seekable
203 [_timeSlider setEnabled:seekable];
204 [_forwardButton setEnabled:seekable];
205 [_backwardButton setEnabled:seekable];
208 - (void)setVolumeLevel:(int)value
210 [_volumeSlider setIntValue:value];
214 #pragma mark Window interactions
218 [NSAnimationContext beginGrouping];
219 [[NSAnimationContext currentContext] setDuration:0.7f];
220 [[self.window animator] setAlphaValue:1.0f];
221 [NSAnimationContext endGrouping];
223 [self startAutohideTimer];
228 [NSAnimationContext beginGrouping];
229 [[NSAnimationContext currentContext] setDuration:0.7f];
230 [[self.window animator] setAlphaValue:0.0f];
231 [NSAnimationContext endGrouping];
234 - (void)centerWindowOnScreen:(CGDirectDisplayID)screenID
236 /* Find screen by its ID */
237 NSScreen *screen = [NSScreen screenWithDisplayID:screenID];
239 /* Check screen validity, fallback to mainScreen */
241 screen = [NSScreen mainScreen];
243 NSRect screenFrame = [screen frame];
244 NSRect windowFrame = [self.window frame];
246 /* Calculate coordinates for new NSWindow position */
248 coordinates.x = (screenFrame.size.width - windowFrame.size.width) / 2 + screenFrame.origin.x;
249 coordinates.y = (screenFrame.size.height / 3) - windowFrame.size.height + screenFrame.origin.y;
251 [self.window setFrameTopLeftPoint:coordinates];
256 [self centerWindowOnScreen:_displayID];
261 [self.window orderOut:self];
266 [self.window orderFront:self];
270 #pragma mark Misc interactions
274 [NSCursor setHiddenUntilMouseMoves:YES];
277 - (void)setVoutWasUpdated:(VLCWindow *)voutWindow
279 _voutWindow = voutWindow;
280 int newDisplayID = [[self.window screen] displayID];
282 if (_displayID != newDisplayID) {
283 _displayID = newDisplayID;
289 #pragma mark Autohide timer management
291 - (void)startAutohideTimer
293 /* Do nothing if timer is already in place */
297 int _timeToKeepVisibleInSec = var_CreateGetInteger(getIntf(), "mouse-hide-timeout") / 1000;
298 _hideTimer = [NSTimer scheduledTimerWithTimeInterval:_timeToKeepVisibleInSec
300 selector:@selector(autohideCallback:)
306 - (void)stopAutohideTimer
308 [_hideTimer invalidate];
312 - (void)autohideCallback:(NSTimer *)timer
314 if (!NSPointInRect([NSEvent mouseLocation], [self.window frame])) {
325 Create an image mask for the NSVisualEffectView
326 with rounded corners in the given rect
328 This is necessary as clipping the VisualEffectView using the layers
329 rounded corners is not possible when using the NSColor clearColor
332 \note The returned image will have the necessary \c capInsets and
333 \c capResizingMode set.
335 \param bounds The rect for the image size
337 - (NSImage *)maskImageWithBounds:(NSRect)bounds
339 static const float radius = 8.0;
340 NSImage *img = [NSImage imageWithSize:bounds.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
341 NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:radius yRadius:radius];
342 [[NSColor blackColor] setFill];
346 [img setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
347 [img setResizingMode:NSImageResizingModeStretch];
352 Injects the visual effect view in the Windows view hierarchy
354 This is necessary as we can't use the NSVisualEffect view on
355 all macOS Versions and therefore need to dynamically insert it.
357 \warning Never call both, \c injectVisualEffectView and \c injectBackgroundView
359 - (void)injectVisualEffectView
362 NSVisualEffectView *view = [[NSVisualEffectView alloc] initWithFrame:self.window.contentView.frame];
363 [view setMaskImage:[self maskImageWithBounds:self.window.contentView.bounds]];
364 [view setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
365 [view setMaterial:NSVisualEffectMaterialDark];
366 [view setState:NSVisualEffectStateActive];
367 [view setAutoresizesSubviews:YES];
369 /* Inject view in view hierarchy */
370 [self.window setContentView:view];
371 [_controllsView setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
372 [self.window.contentView addSubview:_controllsView];
376 Injects the standard background view in the Windows view hierarchy
378 This is necessary on macOS versions that do not support the
379 NSVisualEffectView that usually is injected.
381 \warning Never call both, \c injectVisualEffectView and \c injectBackgroundView
383 - (void)injectBackgroundView
386 NSColor *color = [NSColor colorWithCalibratedWhite:0.0 alpha:0.8];
387 NSView *view = [[NSView alloc] initWithFrame:self.window.contentView.frame];
388 [view setWantsLayer:YES];
389 [view.layer setBackgroundColor:[color CGColor]];
390 [view.layer setCornerRadius:8.0];
391 [view setAutoresizesSubviews:YES];
393 /* Inject view in view hierarchy */
394 [self.window setContentView:view];
395 [self.window.contentView addSubview:_controllsView];
400 [self stopAutohideTimer];