VLCMovieViewController.m 64.8 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
#import "VLCAppDelegate.h"
31
#import "VLCStatusLabel.h"
32
#import "VLCMovieViewControlPanelView.h"
33
#import "VLCSlider.h"
34

35
#import "VLCTrackSelectorView.h"
36
#import "VLCMetadata.h"
37
#import "UIDevice+VLC.h"
38
#import "VLC_iOS-Swift.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
39

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

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

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

57
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate, VLCPlaybackControllerDelegate, VLCDeviceMotionDelegate>
58
{
59 60 61 62 63 64 65 66 67
    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

68
    BOOL _viewAppeared;
69
    BOOL _displayRemainingTime;
70
    BOOL _positionSet;
71
    BOOL _isScrubbing;
72
    BOOL _interfaceIsLocked;
73
    BOOL _audioOnly;
Ahmad Harb's avatar
Ahmad Harb committed
74

75 76 77 78 79
    BOOL _volumeGestureEnabled;
    BOOL _playPauseGestureEnabled;
    BOOL _brightnessGestureEnabled;
    BOOL _seekGestureEnabled;
    BOOL _closeGestureEnabled;
80
    BOOL _variableJumpDurationEnabled;
81
    BOOL _playbackWillClose;
82 83
    BOOL _isTapSeeking;
    VLCMovieJumpState _previousJumpState;
Soomin Lee's avatar
Soomin Lee committed
84

85
    UIPinchGestureRecognizer *_pinchRecognizer;
86
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
87 88 89
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
90 91
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
92
    UITapGestureRecognizer *_tapRecognizer;
93
    UITapGestureRecognizer *_tapOnVideoRecognizer;
94
    UITapGestureRecognizer *_tapToToggleiPhoneXRatioRecognizer;
95
    UITapGestureRecognizer *_tapToSeekRecognizer;
96

97 98
    UIButton *_doneButton;

99
    VLCTrackSelectorView *_trackSelectorContainer;
100 101

    VLCEqualizerView *_equalizerView;
102
    VLCMultiSelectionMenuView *_multiSelectionView;
103

104 105
    VLCPlaybackController *_vpc;

106 107
    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
108
    NSTimer *_sleepCountDownTimer;
109

110
    NSInteger _mediaDuration;
111
    NSInteger _numberOfTapSeek;
Soomin Lee's avatar
Soomin Lee committed
112

113
    VLCDeviceMotion *_deviceMotion;
Soomin Lee's avatar
Soomin Lee committed
114 115
    CGFloat _fov;
    CGPoint _saveLocation;
116
    CGSize _screenPixelSize;
117
}
118
@property (nonatomic, strong) VLCMovieViewControlPanelView *controllerPanel;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
119 120
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
121 122
@end

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

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

132
- (void)viewDidLoad
Felix Paul Kühne's avatar
Felix Paul Kühne committed
133
{
134
    [super viewDidLoad];
135 136
    CGRect rect;

137 138
    _vpc = [VLCPlaybackController sharedInstance];

139 140
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
141

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

161
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
162 163
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
164

165 166 167 168 169 170
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

171
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
172

173 174
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
175 176 177 178 179
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
180 181 182 183
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
184 185 186 187
    [center addObserver:self
               selector:@selector(playbackDidStop:)
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
188

189 190
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
191
    if ([[UIDevice currentDevice] VLCHasExternalDisplay])
192
        [self showOnExternalDisplay];
193

194 195
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

196
    _movieView.userInteractionEnabled = NO;
197 198 199 200 201 202 203 204 205 206 207

    [self setupGestureRecognizers];

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

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

208
    [self.movieView setAccessibilityIdentifier:@"Video Player Title"];
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    [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
231
    [self sleepTimerInitializer];
232 233 234
    [self setupControlPanel];

    CGRect screenBounds = [[UIScreen mainScreen] bounds];
235
    _screenPixelSize = CGSizeMake(screenBounds.size.width, screenBounds.size.height);
236 237

    [self setupConstraints];
238

239 240 241 242
}

- (void)setupGestureRecognizers
{
243 244 245
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
246

247 248 249
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;

250
    if ([[UIDevice currentDevice] isiPhoneX]) {
251
        _tapToToggleiPhoneXRatioRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:_vpc action:@selector(switchIPhoneXFullScreen)];
252 253 254 255 256 257
        _tapToToggleiPhoneXRatioRecognizer.numberOfTapsRequired = 2;
        [self.view addGestureRecognizer:_tapToToggleiPhoneXRatioRecognizer];
    } else {
        _tapToSeekRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToSeekRecognized:)];
        [_tapToSeekRecognizer setNumberOfTapsRequired:2];
        [self.view addGestureRecognizer:_tapToSeekRecognizer];
258
        [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];
259 260
    }
    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(togglePlayPause)];
261
    [_tapRecognizer setNumberOfTouchesRequired:2];
262 263

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

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

279
    [self.view addGestureRecognizer:_pinchRecognizer];
280 281
    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
282 283
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
284 285
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
286

287 288
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
289 290
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
291 292 293 294

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
295 296
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
297
    _tapRecognizer.delegate = self;
298
    _tapToSeekRecognizer.delegate = self;
299 300 301 302 303 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
}

- (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
{
329 330 331
    NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[panel]|"
                                                                    options:0
                                                                    metrics:nil
332
                                                                      views:@{@"panel":_controllerPanel}];
333 334 335 336

    NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[panel]|"
                                                                    options:0
                                                                    metrics:nil
337
                                                                      views:@{@"panel":_controllerPanel}];
338 339
    [self.view addConstraints:hConstraints];
    [self.view addConstraints:vConstraints];
340 341

    //constraint within _trackSelectorContainer is setting it's height to the tableviews contentview
342 343
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:2.0/3.0 constant:0];
    widthConstraint.priority = UILayoutPriorityRequired - 1;
344 345 346
    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],
347 348
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1 constant:420.0],
                             widthConstraint,
349 350 351
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:2.0/3.0 constant:0],
                             ];
    [NSLayoutConstraint activateConstraints:constraints];
352 353 354 355 356

}

- (void)setupNavigationbar
{
357 358
    //Needs to be a UIButton since we need it to work with constraints
    _doneButton = [[UIButton alloc] initWithFrame:CGRectZero];
359
    [_doneButton setAccessibilityIdentifier:@"Done"];
360 361 362 363
    [_doneButton addTarget:self action:@selector(closePlayback:) forControlEvents:UIControlEventTouchUpInside];
    [_doneButton setTitle:NSLocalizedString(@"BUTTON_DONE", nil) forState:UIControlStateNormal];
    [_doneButton setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
    _doneButton.translatesAutoresizingMaskIntoConstraints = NO;
364 365 366 367 368

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

    [self.navigationController.navigationBar addSubview:self.timeNavigationTitleView];
369
    [self.navigationController.navigationBar addSubview:_doneButton];
370

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 377 378
                                                      [NSLayoutConstraint constraintWithItem:_doneButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeLeft multiplier:1 constant:8],
                                                      [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 387 388 389 390 391 392 393
- (void)resetVideoFiltersSliders
{
    _brightnessSlider.value = 1.;
    _contrastSlider.value = 1.;
    _hueSlider.value = 0.;
    _saturationSlider.value = 1.;
    _gammaSlider.value = 1.;
}

394 395
- (void)viewWillAppear:(BOOL)animated
{
396
    [super viewWillAppear:animated];
397

398 399
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
400 401 402
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
403 404 405 406 407 408
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

409
    [self setControlsHidden:NO animated:animated];
410 411

    [self updateDefaults];
Soomin Lee's avatar
Soomin Lee committed
412

413
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
414 415 416 417 418
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
419
    _viewAppeared = YES;
420
    _playbackWillClose = NO;
421

422
    [_vpc recoverDisplayedMetadata];
423
    [self resetVideoFiltersSliders];
424 425
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
426
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
Soomin Lee's avatar
Soomin Lee committed
427 428

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
429
    [self setupForMediaProjection];
430 431
}

432
- (void)viewDidLayoutSubviews
433
{
434 435 436 437 438
    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;

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

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

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

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

471
    _viewAppeared = NO;
472
    if (_idleTimer) {
473
        [_idleTimer invalidate];
474 475
        _idleTimer = nil;
    }
476

477
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
478
    [super viewWillDisappear:animated];
479

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

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

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

490 491
    if (_interfaceIsLocked)
        [self toggleUILock];
492 493 494 495 496
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

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

501 502 503 504 505 506
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [self.deviceMotion stopDeviceMotion];
}

507 508 509 510 511
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

512 513
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
514
    [self setControlsHidden:YES animated:flag];
515 516 517
    [super dismissViewControllerAnimated:flag completion:completion];
}

518
- (void)updateDefaults
519 520
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
521 522 523 524 525

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
526 527 528 529 530 531 532 533 534

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

535 536
#pragma mark - Initializer helper

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

546 547 548 549 550 551 552 553 554
    //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;

555
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
556 557
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
558 559 560 561 562
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
563 564 565 566 567
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
568
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_RESET", nil) forState:UIControlStateNormal];
569 570 571 572 573 574 575 576
    [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];
577
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_SET", nil) forState:UIControlStateNormal];
578 579 580 581 582
    [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];
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608

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

609 610
#pragma mark - controls visibility

611 612
- (NSArray *)itemsForInterfaceLock
{
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    NSMutableArray *items = [[NSMutableArray alloc] initWithArray: @[_pinchRecognizer,
                                                                     _panRecognizer,
                                                                     _tapRecognizer,
                                                                     _doneButton,
                                                                     _timeNavigationTitleView.minimizePlaybackButton,
                                                                     _timeNavigationTitleView.positionSlider,
                                                                     _timeNavigationTitleView.aspectRatioButton,
                                                                     _controllerPanel.playbackSpeedButton,
                                                                     _controllerPanel.trackSwitcherButton,
                                                                     _controllerPanel.bwdButton,
                                                                     _controllerPanel.playPauseButton,
                                                                     _controllerPanel.fwdButton,
                                                                     _controllerPanel.videoFilterButton,
                                                                     _multiSelectionView.equalizerButton,
                                                                     _multiSelectionView.chapterSelectorButton,
                                                                     _multiSelectionView.repeatButton,
                                                                     _multiSelectionView.shuffleButton,
                                                                     _controllerPanel.volumeView]];

    [[UIDevice currentDevice] isiPhoneX] ? [items addObject:_tapToToggleiPhoneXRatioRecognizer]
                                         : [items addObject:_tapToSeekRecognizer];

    return [items copy];
636 637
}

638 639
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
640
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenPixelSize.width);
Soomin Lee's avatar
Soomin Lee committed
641

642 643 644
    if ([_vpc currentMediaIs360Video]) {
        [self zoom360Video:diff];
    } else if (recognizer.velocity < 0. && _closeGestureEnabled) {
645
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
646
    }
647 648
}

649 650 651
- (void)zoom360Video:(CGFloat)zoom
{
    if ([_vpc updateViewpoint:0 pitch:0 roll:0 fov:zoom absolute:NO]) {
652
        //clamp Fov between min and max fov
653 654 655 656
        _fov = MAX(MIN(_fov + zoom, MAX_FOV), MIN_FOV);
    }
}

657 658
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
659
    if (touch.view != self.view)
660 661 662 663 664
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
665 666 667 668 669
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

670
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
671
{
672
    _controlsHidden = hidden;
673
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
674

675
    if (!_controlsHidden) {
676 677
        self.navigationController.navigationBar.alpha = 0.0;
        self.navigationController.navigationBar.hidden = NO;
678
        _controllerPanel.alpha = 0.0f;
679
        _controllerPanel.hidden = !_videoFiltersHidden;
680 681
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
682 683
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
684 685
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
686 687
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
688 689 690 691
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
692 693
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
694 695 696 697

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
698
    }
699

700
    void (^animationBlock)() = ^() {
701
        self.navigationController.navigationBar.alpha = alpha;
702
        _controllerPanel.alpha = alpha;
703
        _videoFilterView.alpha = alpha;
704
        _playbackSpeedView.alpha = alpha;
705
        _trackSelectorContainer.alpha = alpha;
706
        _equalizerView.alpha = alpha;
707
        _multiSelectionView.alpha = alpha;
708 709
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
710 711 712 713 714

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
715
    };
716

717
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
718
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
719
        _videoFilterView.hidden = _videoFiltersHidden;
720
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
721
        self.navigationController.navigationBar.hidden = _controlsHidden;
722
        _trackSelectorContainer.hidden = YES;
723
        _equalizerView.hidden = YES;
724 725
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
726
        _multiSelectionView.hidden = YES;
727 728 729 730

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
731
    };
732

733 734
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
735 736

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
737 738
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
739

740
- (void)toggleControlsVisible
741
{
742 743 744 745 746
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

747 748 749
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

750 751 752
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

753
    [self setControlsHidden:!_controlsHidden animated:YES];
754 755
}

756
- (void)_resetIdleTimer
757 758
{
    if (!_idleTimer)
759
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
760 761 762 763 764
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
765 766
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
767 768 769
    }
}

770 771 772 773 774 775 776 777 778 779 780 781
- (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];
782

783 784 785
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
786
        [_vpc jumpForward:SHORT_JUMP_DURATION];
787 788 789
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
790
        [_vpc jumpBackward:SHORT_JUMP_DURATION];
791 792 793 794 795 796 797 798 799
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

800 801
- (void)idleTimerExceeded
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
802 803 804 805 806
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

807 808
    _idleTimer = nil;
    if (!_controlsHidden)
809
        [self toggleControlsVisible];
810

811 812 813 814 815
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

816 817 818
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

819 820 821
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

822 823 824
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
825 826
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
827 828
}

829 830
- (UIResponder *)nextResponder
{
831
    [self _resetIdleTimer];
832 833 834
    return [super nextResponder];
}

835
- (VLCDeviceMotion *)deviceMotion
Soomin Lee's avatar
Soomin Lee committed
836
{
837 838 839 840 841
    if (!_deviceMotion) {
        _deviceMotion = [VLCDeviceMotion new];
        _deviceMotion.delegate = self;
    }
    return _deviceMotion;
Soomin Lee's avatar
Soomin Lee committed
842 843
}

844 845
- (void)setupForMediaProjection
{
846
    BOOL mediaHasProjection = [_vpc currentMediaIs360Video];
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
    _fov = mediaHasProjection ? DEFAULT_FOV : 0.f;

    [_panRecognizer setEnabled:mediaHasProjection];
    [_swipeRecognizerUp setEnabled:!mediaHasProjection];
    [_swipeRecognizerDown setEnabled:!mediaHasProjection];
    [_swipeRecognizerLeft setEnabled:!mediaHasProjection];
    [_swipeRecognizerRight setEnabled:!mediaHasProjection];

    if (mediaHasProjection) {
        [self.deviceMotion startDeviceMotion];
    }
}

- (void)deviceMotionHasAttitudeWithDeviceMotion:(VLCDeviceMotion *)deviceMotion pitch:(double)pitch yaw:(double)yaw roll:(double)roll
{
862 863 864
    if (_panRecognizer.state != UIGestureRecognizerStateChanged || UIGestureRecognizerStateBegan) {
        [_vpc updateViewpoint:yaw pitch:pitch roll:roll fov:_fov absolute:YES];
    }
865
}
866 867 868 869
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
870
    _playbackWillClose = YES;
871
    [_vpc stopPlayback];
872
}
873

874 875
- (IBAction)minimizePlayback:(id)sender
{
876
    [_delegate movieViewControllerDidSelectMinimize:self];
877
}
878

879 880
- (IBAction)positionSliderAction:(UISlider *)sender
{
881 882 883
    /* 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. */