VLCMovieViewController.m 67.5 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2017 VideoLAN. All rights reserved.
6 7 8 9
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
10 11
 *          Carola Nitz <caro # videolan.org>
 *          Tobias Conradi <videolan # tobias-conradi.de>
12 13 14
 *          Ahmad Harb <harb.dev.leb # gmail.com>
 *          Fabio Ritrovato <sephiroth87 # videolan.org>
 *          Pierre SAGASPE <pierre.sagaspe # me.com>
15 16 17 18
 *          Filipe Cabecinhas <vlc # filcab dot net>
 *          Marc Etcheverry <marc # taplightsoftware dot com>
 *          Christopher Loessl <cloessl # x-berg dot de>
 *          Sylver Bruneau <sylver.bruneau # gmail dot com>
19 20 21
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
22

23
#import "VLCMovieViewController.h"
24
#import "VLCExternalDisplayController.h"
25 26
#import "VLCTrackSelectorTableViewCell.h"
#import "VLCTrackSelectorHeaderView.h"
27
#import "VLCEqualizerView.h"
28
#import "VLCMultiSelectionMenuView.h"
29 30
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
31
#import "VLCTimeNavigationTitleView.h"
32 33
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
34
#import "VLCStatusLabel.h"
35
#import "VLCMovieViewControlPanelView.h"
36
#import "VLCSlider.h"
37
#import "VLCLibraryViewController.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
38

39 40
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
41
#define SHORT_JUMP_DURATION 10
42

Soomin Lee's avatar
Soomin Lee committed
43 44 45 46 47
#define ZOOM_SENSITIVITY 2.99f
#define DEFAULT_FOV 80.f
#define MAX_FOV 150.f
#define MIN_FOV 20.f

48 49 50
#define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
#define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"

51 52 53 54
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

55 56 57 58 59
typedef NS_ENUM(NSInteger, VLCPanType) {
  VLCPanTypeNone,
  VLCPanTypeBrightness,
  VLCPanTypeSeek,
  VLCPanTypeVolume,
Soomin Lee's avatar
Soomin Lee committed
60
  VLCPanTypeProjection
61 62
};

63
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, UITableViewDataSource, UITableViewDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate>
64
{
65 66 67 68 69 70 71 72 73
    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

74
    BOOL _viewAppeared;
75
    BOOL _displayRemainingTime;
76
    BOOL _positionSet;
77
    BOOL _playerIsSetup;
78
    BOOL _isScrubbing;
79
    BOOL _interfaceIsLocked;
80
    BOOL _switchingTracksNotChapters;
81
    BOOL _audioOnly;
Ahmad Harb's avatar
Ahmad Harb committed
82

83 84 85 86 87
    BOOL _volumeGestureEnabled;
    BOOL _playPauseGestureEnabled;
    BOOL _brightnessGestureEnabled;
    BOOL _seekGestureEnabled;
    BOOL _closeGestureEnabled;
88
    BOOL _variableJumpDurationEnabled;
Soomin Lee's avatar
Soomin Lee committed
89
    BOOL _mediaHasProjection;
90
    BOOL _playbackWillClose;
91 92
    BOOL _isTapSeeking;
    VLCMovieJumpState _previousJumpState;
Soomin Lee's avatar
Soomin Lee committed
93

94
    UIPinchGestureRecognizer *_pinchRecognizer;
95
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
96 97 98
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
99 100
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
101
    UITapGestureRecognizer *_tapRecognizer;
102
    UITapGestureRecognizer *_tapOnVideoRecognizer;
103
    UITapGestureRecognizer *_tapToSeekRecognizer;
104 105 106

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
107 108

    VLCEqualizerView *_equalizerView;
109
    VLCMultiSelectionMenuView *_multiSelectionView;
110

111 112
    VLCPlaybackController *_vpc;

113 114
    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
115
    NSTimer *_sleepCountDownTimer;
116

117
    NSInteger _mediaDuration;
118
    NSInteger _numberOfTapSeek;
Soomin Lee's avatar
Soomin Lee committed
119 120 121 122

    CGFloat _fov;
    CGPoint _saveLocation;
    CGSize _screenSizePixel;
123 124
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
125 126
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
127 128
@end

129
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
130

131 132 133 134 135 136 137
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
138 139
- (void)dealloc
{
140
    [[NSNotificationCenter defaultCenter] removeObserver:self];
141 142
}

143
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
144
{
145
    [super viewDidLoad];
146 147
    CGRect rect;

148 149
    int deviceSpeedCategory = [[UIDevice currentDevice] VLCSpeedCategory];

150 151
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
152

153 154
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
155
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
156
    _hueSlider.accessibilityLabel = _hueLabel.text;
157
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
158
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
159
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
160
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
161
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
162
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
163
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
164
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
165
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
166
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
167 168 169 170
    _audioDelayLabel.text = NSLocalizedString(@"AUDIO_DELAY", nil);
    _audioDelaySlider.accessibilityLabel = _audioDelayLabel.text;
    _spuDelayLabel.text = NSLocalizedString(@"SPU_DELAY", nil);
    _spuDelaySlider.accessibilityLabel = _spuDelayLabel.text;
171

172
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
173 174
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
175

176 177 178 179 180 181
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

182
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
183

184 185
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
186

187 188 189 190 191
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
192 193
    [center addObserver:self selector:@selector(screenBrightnessChanged:)
                   name:UIScreenBrightnessDidChangeNotification object:nil];
194 195 196 197
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
198 199 200 201
    [center addObserver:self
               selector:@selector(playbackDidStop:)
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
202

203 204
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
205
    if ([[UIDevice currentDevice] VLCHasExternalDisplay])
206
        [self showOnExternalDisplay];
207

208 209
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

210
    _movieView.userInteractionEnabled = NO;
211 212 213
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
214

215 216 217
    _tapToSeekRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToSeekRecognized:)];
    [_tapToSeekRecognizer setNumberOfTapsRequired:2];

218 219 220 221 222 223
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:_pinchRecognizer];

    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
    [_tapRecognizer setNumberOfTouchesRequired:2];
224 225

    _currentPanType = VLCPanTypeNone;
226 227 228 229
    _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
    [_panRecognizer setMinimumNumberOfTouches:1];
    [_panRecognizer setMaximumNumberOfTouches:1];

230 231
    [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];

232 233 234 235
    _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
236 237 238 239 240 241
    _swipeRecognizerUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerUp.direction = UISwipeGestureRecognizerDirectionUp;
    _swipeRecognizerUp.numberOfTouchesRequired = 2;
    _swipeRecognizerDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerDown.direction = UISwipeGestureRecognizerDirectionDown;
    _swipeRecognizerDown.numberOfTouchesRequired = 2;
242

243

244 245
    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
246 247
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
248 249
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
250 251
    [self.view addGestureRecognizer:_tapToSeekRecognizer];

252 253
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
254 255
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
256 257 258 259

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
260 261
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
262
    _tapRecognizer.delegate = self;
263 264 265 266 267
    _tapToSeekRecognizer.delegate = self;

    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;
268

Felix Paul Kühne's avatar
Felix Paul Kühne committed
269 270 271 272 273 274 275 276 277 278 279
    self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
    self.toolbar.tintColor = [UIColor whiteColor];
    self.toolbar.barStyle = UIBarStyleBlack;

    rect = self.resetVideoFilterButton.frame;
    rect.origin.y = rect.origin.y + 5.;
    self.resetVideoFilterButton.frame = rect;
    rect = self.toolbar.frame;
    rect.size.height = rect.size.height + rect.origin.y;
    rect.origin.y = 0;
    self.toolbar.frame = rect;
280

281
    _playerIsSetup = NO;
282

283 284
    [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
    [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
285 286

    rect = self.view.frame;
287 288 289 290 291 292
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
293
        width = 420.;
294 295
        height = 470.;
    }
296

297
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
298 299
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
300
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
301
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
302 303 304
    _trackSelectorTableView.rowHeight = 44.;
    _trackSelectorTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    _trackSelectorTableView.sectionHeaderHeight = 28.;
305 306
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
307
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
308

309
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
310
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
311
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
312 313
    _trackSelectorContainer.hidden = YES;

314
    if (deviceSpeedCategory >= 3) {
315 316 317
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
318
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
319
    _trackSelectorTableView.allowsMultipleSelection = YES;
320 321

    [self.view addSubview:_trackSelectorContainer];
322

323
    _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake(0, 0, 450., 240.)];
324
    _equalizerView.delegate = [VLCPlaybackController sharedInstance];
325
    _equalizerView.UIdelegate = self;
326 327 328
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
329

330 331
    //Sleep Timer initialization
    [self sleepTimerInitializer:deviceSpeedCategory];
332
    [self setupControlPanel];
333

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGFloat screenScale = [[UIScreen mainScreen] scale];
    _screenSizePixel = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
    _saveLocation = CGPointMake(-1.f, -1.f);

    [self setupConstraints];
}

- (void)setupControlPanel
{
    _controllerPanel = [[VLCMovieViewControlPanelView alloc] initWithFrame:CGRectZero];
    [_controllerPanel.bwdButton addTarget:self action:@selector(backward:) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.fwdButton addTarget:self action:@selector(forward:) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.playPauseButton addTarget:self action:@selector(playPause) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.moreActionsButton addTarget:self action:@selector(moreActions:) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.playbackSpeedButton addTarget:self action:@selector(showPlaybackSpeedView) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.trackSwitcherButton addTarget:self action:@selector(switchTrack:) forControlEvents:UIControlEventTouchUpInside];
    [_controllerPanel.videoFilterButton addTarget:self action:@selector(videoFilterToggle:) forControlEvents:UIControlEventTouchUpInside];

    // HACK: get the slider from volume view
    UISlider *volumeSlider = nil;
    for (id aView in _controllerPanel.volumeView.subviews){
        if ([aView isKindOfClass:[UISlider class]]){
            volumeSlider = (UISlider *)aView;
            break;
        }
    }
    [volumeSlider addTarget:self action:@selector(volumeSliderAction:) forControlEvents:UIControlEventValueChanged];

    _controllerPanel.translatesAutoresizingMaskIntoConstraints = NO;

    [self.view addSubview:_controllerPanel];
}

- (void)setupConstraints
{
370 371 372
    NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[panel]|"
                                                                    options:0
                                                                    metrics:nil
373
                                                                      views:@{@"panel":_controllerPanel}];
374 375 376 377

    NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[panel]|"
                                                                    options:0
                                                                    metrics:nil
378
                                                                      views:@{@"panel":_controllerPanel}];
379 380
    [self.view addConstraints:hConstraints];
    [self.view addConstraints:vConstraints];
381
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
382

383 384
- (void)viewWillAppear:(BOOL)animated
{
385
    [super viewWillAppear:animated];
386

387 388 389 390 391 392
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

393
    [self.navigationController setNavigationBarHidden:YES animated:animated];
394

395 396 397
    _vpc = [VLCPlaybackController sharedInstance];
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
398

399
    [self screenBrightnessChanged:nil];
400
    [self setControlsHidden:NO animated:animated];
401 402 403

    [self updateDefaults];
    [NSUserDefaults standardUserDefaults];
Soomin Lee's avatar
Soomin Lee committed
404 405 406

    //Disabling video gestures, media not init in the player yet.
    [self enableNormalVideoGestures:NO];
407
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
408 409 410 411 412
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
413
    _viewAppeared = YES;
414
    _playbackWillClose = NO;
415

416 417 418 419
    [_vpc recoverDisplayedMetadata];
    _vpc.videoOutputView = nil;
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
420
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
Soomin Lee's avatar
Soomin Lee committed
421 422 423 424 425 426 427 428 429 430

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
    _fov = 0.f;
    _mediaHasProjection = NO;
    if ([_vpc currentMediaProjection] == VLCMediaProjectionEquiRectangular) {
        _fov = DEFAULT_FOV;
        _mediaHasProjection = YES;
    }

    [self enableNormalVideoGestures:!_mediaHasProjection];
431 432
}

433
- (void)viewDidLayoutSubviews
434
{
435 436 437 438 439
    CGRect equalizerRect = _equalizerView.frame;
    equalizerRect.origin.x = CGRectGetMidX(self.view.bounds) - CGRectGetWidth(equalizerRect)/2.0;
    equalizerRect.origin.y = CGRectGetMidY(self.view.bounds) - CGRectGetHeight(equalizerRect)/2.0;
    _equalizerView.frame = equalizerRect;

440
    CGRect multiSelectionFrame;
441
    CGRect controllerPanelFrame = _controllerPanel.frame;;
442 443 444 445 446 447 448 449 450 451

    if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone) {
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
        _multiSelectionView.frame = multiSelectionFrame;
        _multiSelectionView.showsEqualizer = YES;
        return;
    }

452
    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
453 454 455 456 457 458 459 460 461
        _multiSelectionView.showsEqualizer = YES;
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
    } else {
        _multiSelectionView.showsEqualizer = NO;
        multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
        multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
        multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
462
    }
463
    _multiSelectionView.frame = multiSelectionFrame;
464 465
}

466
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
467
{
468 469
    if (_vpc.videoOutputView == self.movieView) {
        _vpc.videoOutputView = nil;
470
    }
471

472
    _viewAppeared = NO;
473
    if (_idleTimer) {
474
        [_idleTimer invalidate];
475 476
        _idleTimer = nil;
    }
477
    [self.navigationController setNavigationBarHidden:NO animated:YES];
478
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
479
    [super viewWillDisappear:animated];
480

Felix Paul Kühne's avatar
Felix Paul Kühne committed
481 482 483 484
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

485 486 487
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
488 489
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
490

491 492 493 494 495
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

496
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
497
    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
498 499 500 501 502 503 504
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

505 506
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
507
    [self setControlsHidden:YES animated:flag];
508 509 510
    [super dismissViewControllerAnimated:flag completion:completion];
}

511
- (void)updateDefaults
512 513
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
514 515 516 517 518

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
519 520 521 522 523 524 525 526 527

    _volumeGestureEnabled = [[defaults objectForKey:kVLCSettingVolumeGesture] boolValue];
    _playPauseGestureEnabled = [[defaults objectForKey:kVLCSettingPlayPauseGesture] boolValue];
    _brightnessGestureEnabled = [[defaults objectForKey:kVLCSettingBrightnessGesture] boolValue];
    _seekGestureEnabled = [[defaults objectForKey:kVLCSettingSeekGesture] boolValue];
    _closeGestureEnabled = [[defaults objectForKey:kVLCSettingCloseGesture] boolValue];
    _variableJumpDurationEnabled = [[defaults objectForKey:kVLCSettingVariableJumpDuration] boolValue];
}

528 529 530 531 532 533 534
#pragma mark - Initializer helper

- (void)sleepTimerInitializer:(int)deviceSpeedCategory
{
    /* add sleep timer UI */
    _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 200.)];
    _sleepTimerContainer.center = self.view.center;
535 536
    _sleepTimerContainer.clipsToBounds = YES;
    _sleepTimerContainer.layer.cornerRadius = 5;
537 538
    _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

539 540 541 542 543 544 545 546 547
    //Layers used to create a separator for the buttons.
    CALayer *horizontalSeparator = [CALayer layer];
    horizontalSeparator.frame = CGRectMake(0., 162., 300., 1.);
    horizontalSeparator.backgroundColor = [UIColor VLCLightTextColor].CGColor;

    CALayer *verticalSeparator = [CALayer layer];
    verticalSeparator.frame = CGRectMake(150., 162., 1., 48.);
    verticalSeparator.backgroundColor = [UIColor VLCLightTextColor].CGColor;

548 549 550 551 552 553 554 555 556 557 558
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
    if (deviceSpeedCategory >= 3) {
        _sleepTimeDatePicker.opaque = NO;
        _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
    } else
        _sleepTimeDatePicker.backgroundColor = [UIColor blackColor];
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) forState:UIControlStateNormal];
    [cancelSleepTimer setTintColor:[UIColor VLCLightTextColor]];
    [cancelSleepTimer setTitleColor:[UIColor VLCDarkTextShadowColor] forState:UIControlStateHighlighted];
    [cancelSleepTimer addTarget:self action:@selector(sleepTimerCancel:) forControlEvents:UIControlEventTouchDown];
    cancelSleepTimer.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 10, 0);
    [_sleepTimerContainer addSubview:cancelSleepTimer];

    UIButton *confirmSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(150., 162., 150., 48.)];
    confirmSleepTimer.backgroundColor = [UIColor clearColor];
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_DONE", nil) forState:UIControlStateNormal];
    [confirmSleepTimer setTintColor:[UIColor VLCLightTextColor]];
    [confirmSleepTimer setTitleColor:[UIColor VLCDarkTextShadowColor] forState:UIControlStateHighlighted];
    [confirmSleepTimer addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventTouchDown];
    confirmSleepTimer.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 10, 0);
    [_sleepTimerContainer addSubview:confirmSleepTimer];
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

    /* adapt the date picker style to suit our needs */
    [_sleepTimeDatePicker setValue:[UIColor whiteColor] forKeyPath:@"textColor"];
    SEL selector = NSSelectorFromString(@"setHighlightsToday:");
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDatePicker instanceMethodSignatureForSelector:selector]];
    BOOL no = NO;
    [invocation setSelector:selector];
    [invocation setArgument:&no atIndex:2];
    [invocation invokeWithTarget:_sleepTimeDatePicker];

    if (_sleepTimerContainer.subviews.count > 0) {
        NSArray *subviewsOfSubview = [_sleepTimeDatePicker.subviews[0] subviews];
        NSUInteger subviewCount = subviewsOfSubview.count;
        for (NSUInteger x = 0; x < subviewCount; x++) {
            if ([subviewsOfSubview[x] isKindOfClass:[UILabel class]])
                [subviewsOfSubview[x] setTextColor:[UIColor VLCLightTextColor]];
        }
    }
    _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
    _sleepTimeDatePicker.minuteInterval = 1;
    _sleepTimeDatePicker.minimumDate = [NSDate date];
    _sleepTimeDatePicker.countDownDuration = 1200.;

    [self.view addSubview:_sleepTimerContainer];
}

605 606
#pragma mark - controls visibility

607 608
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
609 610
    LOCKCHECK;

Soomin Lee's avatar
Soomin Lee committed
611
    if (!_closeGestureEnabled || isnan(recognizer.velocity))
612 613
        return;

Soomin Lee's avatar
Soomin Lee committed
614 615 616 617 618 619 620 621
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenSizePixel.width);

    if (_mediaHasProjection) {
        if ([_vpc updateViewpoint:0 pitch:0 roll:0 fov:diff absolute:NO]) {
            //Checking for fov value in case of
            _fov = MAX(MIN(_fov + diff, MAX_FOV), MIN_FOV);
        }
    } else if (recognizer.velocity < 0.) {
622
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
623
    }
624 625
}

626 627
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
628
    if (touch.view != self.view)
629 630 631 632 633
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
634 635 636 637 638
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

639
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
640
{
641
    _controlsHidden = hidden;
642
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
643

644 645
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
646
        _controllerPanel.hidden = !_videoFiltersHidden;
647 648
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
649 650
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
651 652
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
653 654
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
655 656
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
657 658 659 660
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
661 662
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
663 664 665 666

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
667
    }
668

669 670
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
671
        _toolbar.alpha = alpha;
672
        _videoFilterView.alpha = alpha;
673
        _playbackSpeedView.alpha = alpha;
674
        _trackSelectorContainer.alpha = alpha;
675
        _equalizerView.alpha = alpha;
676
        _multiSelectionView.alpha = alpha;
677 678
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
679 680 681 682 683

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
684
    };
685

686
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
687
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
688
        _toolbar.hidden = _controlsHidden;
689
        _videoFilterView.hidden = _videoFiltersHidden;
690
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
691
        _trackSelectorContainer.hidden = YES;
692
        _equalizerView.hidden = YES;
693 694
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
695
        _multiSelectionView.hidden = YES;
696 697 698 699

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
700
    };
701

702 703
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
704 705

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
706 707
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
708

709
- (void)toggleControlsVisible
710
{
711 712 713 714 715
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

716 717 718
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

719 720 721
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

722
    [self setControlsHidden:!_controlsHidden animated:YES];
723 724
}

725
- (void)_resetIdleTimer
726 727
{
    if (!_idleTimer)
728
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
729 730 731 732 733
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
734 735
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
736 737 738
    }
}

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
- (NSString *)_stringInTimeFormatFrom:(int)duration
{
    if (duration < 60) {
        return [NSString stringWithFormat:@"%is", duration];
    } else {
        return [NSString stringWithFormat:@"%im%is", duration / 60, duration % 60];
    }
}

- (void)_seekFromTap
{
    NSMutableString *hudString = [NSMutableString string];
    VLCMediaPlayer *mediaPlayer = _vpc.mediaPlayer;
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
        [mediaPlayer shortJumpForward];
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
        [mediaPlayer shortJumpBackward];
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

769 770
- (void)idleTimerExceeded
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
771 772 773 774 775
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

776 777
    _idleTimer = nil;
    if (!_controlsHidden)
778
        [self toggleControlsVisible];
779

780 781 782 783 784
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

785 786 787
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

788 789 790
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

791 792 793
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
794 795
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
796 797
}

798 799
- (UIResponder *)nextResponder
{
800
    [self _resetIdleTimer];
801 802 803
    return [super nextResponder];
}

Soomin Lee's avatar
Soomin Lee committed
804 805 806 807 808 809 810 811 812
- (void)enableNormalVideoGestures:(BOOL)enable
{
    [_tapRecognizer setEnabled:enable];
    [_swipeRecognizerUp setEnabled:enable];
    [_swipeRecognizerDown setEnabled:enable];
    [_swipeRecognizerLeft setEnabled:enable];
    [_swipeRecognizerRight setEnabled:enable];
}

813 814 815 816
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
817
    LOCKCHECK;
818
    _playbackWillClose = YES;
819
    [_vpc stopPlayback];
820
}
821

822 823 824 825
- (IBAction)minimizePlayback:(id)sender
{
    LOCKCHECK;
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
826
}
827

828 829
- (IBAction)positionSliderAction:(UISlider *)sender
{
830 831
    LOCKCHECK;

832 833 834
    /* we need to limit the number of events sent by the slider, since otherwise, the user
     * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
     * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
835
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
836
    if (_mediaDuration > 0) {
837 838
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
839
        [self.timeNavigationTitleView setNeedsLayout];
840
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
841 842
        _positionSet = NO;
    }
843
    [self _resetIdleTimer];
844 845
}

846 847 848
- (void)_setPositionForReal
{
    if (!_positionSet) {
849 850
        _vpc.mediaPlayer.position = self.timeNavigationTitleView.positionSlider.value;
        [_vpc setNeedsMetadataUpdate];
851 852 853 854
        _positionSet = YES;
    }
}

855 856
- (IBAction)positionSliderTouchDown:(id)sender
{
857 858
    LOCKCHECK;

859 860
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
861
    _isScrubbing = YES;
862 863 864 865
}

- (IBAction)positionSliderTouchUp:(id)sender
{
866 867
    LOCKCHECK;

868
    self.scrubIndicatorView.hidden = YES;
869
    _isScrubbing = NO;
870 871 872 873
}

- (void)_updateScrubLabel
{
874
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
875
    if (speed == 1.)
876
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
877
    else if (speed == .5)
878
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
879
    else if (speed == .25)
880
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
881
    else
882
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
883 884

    [self _resetIdleTimer];
885 886 887 888