VLCMovieViewController.m 61.2 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
#import "VLCEqualizerView.h"
26
#import "VLCMultiSelectionMenuView.h"
27 28
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
29
#import "VLCTimeNavigationTitleView.h"
30 31
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
32
#import "VLCStatusLabel.h"
33
#import "VLCMovieViewControlPanelView.h"
34
#import "VLCSlider.h"
35
#import "VLCLibraryViewController.h"
36
#import "VLCTrackSelectorView.h"
37
#import "VLCMetadata.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 51
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

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

60
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate>
61
{
62 63 64 65 66 67 68 69 70
    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

71
    BOOL _viewAppeared;
72
    BOOL _displayRemainingTime;
73
    BOOL _positionSet;
74
    BOOL _playerIsSetup;
75
    BOOL _isScrubbing;
76
    BOOL _interfaceIsLocked;
77
    BOOL _audioOnly;
Ahmad Harb's avatar
Ahmad Harb committed
78

79 80 81 82 83
    BOOL _volumeGestureEnabled;
    BOOL _playPauseGestureEnabled;
    BOOL _brightnessGestureEnabled;
    BOOL _seekGestureEnabled;
    BOOL _closeGestureEnabled;
84
    BOOL _variableJumpDurationEnabled;
Soomin Lee's avatar
Soomin Lee committed
85
    BOOL _mediaHasProjection;
86
    BOOL _playbackWillClose;
87 88
    BOOL _isTapSeeking;
    VLCMovieJumpState _previousJumpState;
Soomin Lee's avatar
Soomin Lee committed
89

90
    UIPinchGestureRecognizer *_pinchRecognizer;
91
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
92 93 94
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
95 96
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
97
    UITapGestureRecognizer *_tapRecognizer;
98
    UITapGestureRecognizer *_tapOnVideoRecognizer;
99
    UITapGestureRecognizer *_tapToSeekRecognizer;
100

101
    VLCTrackSelectorView *_trackSelectorContainer;
102 103

    VLCEqualizerView *_equalizerView;
104
    VLCMultiSelectionMenuView *_multiSelectionView;
105

106 107
    VLCPlaybackController *_vpc;

108 109
    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
110
    NSTimer *_sleepCountDownTimer;
111

112
    NSInteger _mediaDuration;
113
    NSInteger _numberOfTapSeek;
Soomin Lee's avatar
Soomin Lee committed
114 115 116 117

    CGFloat _fov;
    CGPoint _saveLocation;
    CGSize _screenSizePixel;
118
}
119
@property (nonatomic, strong) VLCMovieViewControlPanelView *controllerPanel;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
120 121
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
122 123
@end

124
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
125

126 127 128 129 130 131 132
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
133 134
- (void)dealloc
{
135
    [[NSNotificationCenter defaultCenter] removeObserver:self];
136 137
}

138
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
139
{
140
    [super viewDidLoad];
141 142
    CGRect rect;

143 144
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
145

146 147
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
148
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
149
    _hueSlider.accessibilityLabel = _hueLabel.text;
150
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
151
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
152
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
153
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
154
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
155
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
156
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
157
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
158
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
159
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
160 161 162 163
    _audioDelayLabel.text = NSLocalizedString(@"AUDIO_DELAY", nil);
    _audioDelaySlider.accessibilityLabel = _audioDelayLabel.text;
    _spuDelayLabel.text = NSLocalizedString(@"SPU_DELAY", nil);
    _spuDelaySlider.accessibilityLabel = _spuDelayLabel.text;
164

165
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
166 167
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
168

169 170 171 172 173 174
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

175
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
176

177 178
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
179 180 181 182 183
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
184 185
    [center addObserver:self selector:@selector(screenBrightnessChanged:)
                   name:UIScreenBrightnessDidChangeNotification object:nil];
186 187 188 189
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
190 191 192 193
    [center addObserver:self
               selector:@selector(playbackDidStop:)
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
194

195 196
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
197
    if ([[UIDevice currentDevice] VLCHasExternalDisplay])
198
        [self showOnExternalDisplay];
199

200 201
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

202
    _movieView.userInteractionEnabled = NO;
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

    [self setupGestureRecognizers];

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

    rect = self.resetVideoFilterButton.frame;
    rect.origin.y = rect.origin.y + 5.;
    self.resetVideoFilterButton.frame = rect;

    _playerIsSetup = NO;

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

    _trackSelectorContainer = [[VLCTrackSelectorView alloc] initWithFrame:CGRectZero];
    _trackSelectorContainer.hidden = YES;
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
        _trackSelectorContainer.hidden = YES;
    };
    _trackSelectorContainer.completionHandler = completionBlock;
    _trackSelectorContainer.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:_trackSelectorContainer];

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

    //Sleep Timer initialization
238
    [self sleepTimerInitializer];
239 240 241 242 243 244 245 246
    [self setupControlPanel];

    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];
247

248 249 250 251
}

- (void)setupGestureRecognizers
{
252 253 254
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
255

256 257 258
    _tapToSeekRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToSeekRecognized:)];
    [_tapToSeekRecognizer setNumberOfTapsRequired:2];

259 260 261 262 263 264
    _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];
265 266

    _currentPanType = VLCPanTypeNone;
267 268 269 270
    _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
    [_panRecognizer setMinimumNumberOfTouches:1];
    [_panRecognizer setMaximumNumberOfTouches:1];

271 272
    [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];

273 274 275 276
    _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
277 278 279 280 281 282
    _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;
283 284 285

    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
286 287
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
288 289
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
290 291
    [self.view addGestureRecognizer:_tapToSeekRecognizer];

292 293
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
294 295
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
296 297 298 299

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
300 301
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
302
    _tapRecognizer.delegate = self;
303
    _tapToSeekRecognizer.delegate = self;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
}

- (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
{
334 335 336
    NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[panel]|"
                                                                    options:0
                                                                    metrics:nil
337
                                                                      views:@{@"panel":_controllerPanel}];
338 339 340 341

    NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[panel]|"
                                                                    options:0
                                                                    metrics:nil
342
                                                                      views:@{@"panel":_controllerPanel}];
343 344
    [self.view addConstraints:hConstraints];
    [self.view addConstraints:vConstraints];
345 346 347 348 349 350 351 352 353 354

    CGFloat width = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ? 300.0 : 420.0;
    //constraint within _trackSelectorContainer is setting it's height to the tableviews contentview
    NSArray *constraints = @[
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0],
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0],
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1 constant:width],
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:2.0/3.0 constant:0],
                             ];
    [NSLayoutConstraint activateConstraints:constraints];
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

}

- (void)setupNavigationbar
{
    UIButton *doneButton = [[UIButton alloc] initWithFrame:CGRectZero];
    [doneButton addTarget:self action:@selector(closePlayback:) forControlEvents:UIControlEventTouchUpInside];
    [doneButton setTitle:NSLocalizedString(@"BUTTON_DONE", nil) forState:UIControlStateNormal];
    doneButton.translatesAutoresizingMaskIntoConstraints = NO;

    self.timeNavigationTitleView = [[[NSBundle mainBundle] loadNibNamed:@"VLCTimeNavigationTitleView" owner:self options:nil] objectAtIndex:0];
    self.timeNavigationTitleView.translatesAutoresizingMaskIntoConstraints = NO;

    [self.navigationController.navigationBar addSubview:self.timeNavigationTitleView];
    [self.navigationController.navigationBar addSubview:doneButton];

371 372 373 374
    NSObject *guide = self.navigationController.navigationBar;
    if (@available(iOS 11.0, *)) {
        guide = self.navigationController.navigationBar.safeAreaLayoutGuide;
    }
375
    [self.navigationController.view addConstraints: @[
376
                                                      [NSLayoutConstraint constraintWithItem:doneButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeLeft multiplier:1 constant:8],
377 378
                                                      [NSLayoutConstraint constraintWithItem:doneButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeCenterY multiplier:1 constant:0],
                                                      [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:doneButton attribute:NSLayoutAttributeRight multiplier:1 constant:0],
379
                                                      [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeRight multiplier:1 constant:0],
380 381 382
                                                      [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeTop multiplier:1 constant:0],
                                                      [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
                                                      ]];
383
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
384

385 386
- (void)viewWillAppear:(BOOL)animated
{
387
    [super viewWillAppear:animated];
388

389 390 391
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
392 393 394 395 396 397
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

398 399 400
    _vpc = [VLCPlaybackController sharedInstance];
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
401

402
    [self screenBrightnessChanged:nil];
403
    [self setControlsHidden:NO animated:animated];
404 405

    [self updateDefaults];
Soomin Lee's avatar
Soomin Lee committed
406 407 408

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

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

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

    //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];
432

433 434
}

435
- (void)viewDidLayoutSubviews
436
{
437 438 439 440 441
    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;

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

    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;
    }

454
    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
455 456 457 458 459 460 461 462 463
        _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;
464
    }
465
    _multiSelectionView.frame = multiSelectionFrame;
466 467
}

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

474
    _viewAppeared = NO;
475
    if (_idleTimer) {
476
        [_idleTimer invalidate];
477 478
        _idleTimer = nil;
    }
479

480
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
481
    [super viewWillDisappear:animated];
482

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

487 488 489
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

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

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

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

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

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

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

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

    _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];
}

530 531
#pragma mark - Initializer helper

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

541 542 543 544 545 546 547 548 549
    //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;

550
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
551 552
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
553 554 555 556 557
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
    [_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];
578 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

    /* 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];
}

604 605
#pragma mark - controls visibility

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

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

Soomin Lee's avatar
Soomin Lee committed
613 614 615 616 617 618 619 620
    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.) {
621
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
622
    }
623 624
}

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

    return YES;
}

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

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

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

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

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

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

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

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

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

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

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

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

718 719 720
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

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

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

738 739 740 741 742 743 744 745 746 747 748 749
- (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];
750

751 752 753
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
754
        [_vpc jumpForward:10];
755 756 757
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
758
        [_vpc jumpBackward:10];
759 760 761 762 763 764 765 766 767
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

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

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

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

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

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

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

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

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

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

812 813 814 815
#pragma mark - controls

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

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

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

831 832 833
    /* 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. */
834
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
835
    if (_mediaDuration > 0) {
836 837
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
838
        [self.timeNavigationTitleView setNeedsLayout];
839
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
840 841
        _positionSet = NO;
    }
842
    [self _resetIdleTimer];
843 844
}

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

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

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

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

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

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

    [self _resetIdleTimer];
884 885 886 887
}

- (IBAction)positionSliderDrag:(id)sender
{
888 889
    LOCKCHECK;

890 891 892
    [self _updateScrubLabel];
}

893
- (void)volumeSliderAction:(id)sender
894
{
895 896
    LOCKCHECK;

897 898 899
    [self _resetIdleTimer];
}