VLCMovieViewController.m 68 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 "VLCEqualizerView.h"
25
#import "VLCMultiSelectionMenuView.h"
26 27
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
28
#import "VLCTimeNavigationTitleView.h"
29
#import "VLCAppDelegate.h"
30
#import "VLCStatusLabel.h"
31
#import "VLCMovieViewControlPanelView.h"
32
#import "VLCSlider.h"
33

34
#import "VLCTrackSelectorView.h"
35
#import "VLCMetadata.h"
36
#import "UIDevice+VLC.h"
37
#import "VLC-Swift.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

43
#define ZOOM_SENSITIVITY 5.f
44 45 46 47
#define DEFAULT_FOV 80.f
#define MAX_FOV 150.f
#define MIN_FOV 20.f

48 49 50 51 52
typedef NS_ENUM(NSInteger, VLCPanType) {
  VLCPanTypeNone,
  VLCPanTypeBrightness,
  VLCPanTypeSeek,
  VLCPanTypeVolume,
53
  VLCPanTypeProjection
54 55
};

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

    NSTimer *_idleTimer;

64
    BOOL _viewAppeared;
65
    BOOL _displayRemainingTime;
66
    BOOL _positionSet;
67
    BOOL _isScrubbing;
68
    BOOL _interfaceIsLocked;
69
    BOOL _audioOnly;
70

71 72 73 74 75
    BOOL _volumeGestureEnabled;
    BOOL _playPauseGestureEnabled;
    BOOL _brightnessGestureEnabled;
    BOOL _seekGestureEnabled;
    BOOL _closeGestureEnabled;
76
    BOOL _variableJumpDurationEnabled;
77
    BOOL _playbackWillClose;
78 79
    BOOL _isTapSeeking;
    VLCMovieJumpState _previousJumpState;
80
    VLCMediaPlayerState _previousPlayerStateWasPaused;
81

82
    UIPinchGestureRecognizer *_pinchRecognizer;
83
    VLCPanType _currentPanType;
84 85 86
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
87 88
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
89
    UITapGestureRecognizer *_tapRecognizer;
90
    UITapGestureRecognizer *_tapOnVideoRecognizer;
91
    UITapGestureRecognizer *_tapToToggleiPhoneXRatioRecognizer;
92
    UITapGestureRecognizer *_tapToSeekRecognizer;
93

94 95
    UIButton *_doneButton;

96
    VLCTrackSelectorView *_trackSelectorContainer;
97 98

    VLCEqualizerView *_equalizerView;
99
    VLCMultiSelectionMenuView *_multiSelectionView;
100

101 102
    VLCPlaybackController *_vpc;

103 104
    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
105
    NSTimer *_sleepCountDownTimer;
106

107
    NSInteger _mediaDuration;
108
    NSInteger _numberOfTapSeek;
109

110
    VLCDeviceMotion *_deviceMotion;
111 112
    CGFloat _fov;
    CGPoint _saveLocation;
113
    CGSize _screenPixelSize;
114
    UIInterfaceOrientation _lockedOrientation;
115 116 117

    UIStackView *_navigationBarStackView;
    UIButton *_rendererButton;
118
}
119
@property (nonatomic, strong) VLCMovieViewControlPanelView *controllerPanel;
120
@property (nonatomic, strong) VLCService *services;
121
@property (nonatomic, strong) IBOutlet PlayingExternallyView *playingExternalView;
122

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

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

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

134 135 136 137 138 139 140 141 142 143
- (instancetype)initWithServices:(VLCService *)services
{
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        NSAssert([services isKindOfClass:[VLCService class]], @"VLCPlayerDisplayController: Injected services class issue");
        _services = services;
    }
    return self;
}

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

149 150
    _vpc = [VLCPlaybackController sharedInstance];

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

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

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

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

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

185 186
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
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 194 195
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
196 197 198 199
    [center addObserver:self
               selector:@selector(playbackDidStop:)
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
200

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

203
    _movieView.userInteractionEnabled = NO;
204 205 206 207 208 209 210 211 212 213 214

    [self setupGestureRecognizers];

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

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

215
    [self.movieView setAccessibilityIdentifier:@"Video Player Title"];
216 217 218 219 220 221 222 223
    [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];
224
        self->_trackSelectorContainer.hidden = YES;
225 226 227 228 229 230 231 232 233 234 235 236 237
    };
    _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
    [self setupControlPanel];

    CGRect screenBounds = [[UIScreen mainScreen] bounds];
242
    _screenPixelSize = CGSizeMake(screenBounds.size.width, screenBounds.size.height);
243 244

    [self setupConstraints];
245
    [self setupRendererDiscovererManager];
246 247 248 249
}

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

254 255 256
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;

257
    if ([[UIDevice currentDevice] isiPhoneX]) {
258
        _tapToToggleiPhoneXRatioRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:_vpc action:@selector(switchIPhoneXFullScreen)];
259 260 261 262 263 264
        _tapToToggleiPhoneXRatioRecognizer.numberOfTapsRequired = 2;
        [self.view addGestureRecognizer:_tapToToggleiPhoneXRatioRecognizer];
    } else {
        _tapToSeekRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToSeekRecognized:)];
        [_tapToSeekRecognizer setNumberOfTapsRequired:2];
        [self.view addGestureRecognizer:_tapToSeekRecognizer];
265
        [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];
266 267
    }
    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(togglePlayPause)];
268
    [_tapRecognizer setNumberOfTouchesRequired:2];
269 270

    _currentPanType = VLCPanTypeNone;
271 272 273 274 275 276 277 278
    _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;
279 280 281 282 283 284
    _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;
285

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

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

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
302 303
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
304
    _tapRecognizer.delegate = self;
305
    _tapToSeekRecognizer.delegate = self;
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 334 335
}

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

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

    //constraint within _trackSelectorContainer is setting it's height to the tableviews contentview
349 350
    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;
351 352 353
    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],
354 355
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1 constant:420.0],
                             widthConstraint,
356 357 358
                             [NSLayoutConstraint constraintWithItem:_trackSelectorContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:2.0/3.0 constant:0],
                             ];
    [NSLayoutConstraint activateConstraints:constraints];
359 360 361

}

362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
- (UIButton *)doneButton
{
    if (!_doneButton) {
        _doneButton = [[UIButton alloc] initWithFrame:CGRectZero];
        [_doneButton setAccessibilityIdentifier:@"Done"];
        [_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;
    }
    return _doneButton;
}

- (UIStackView *)navigationBarStackView
{
    if (!_navigationBarStackView) {
        _navigationBarStackView = [[UIStackView alloc] init];
        _navigationBarStackView.translatesAutoresizingMaskIntoConstraints = NO;
        _navigationBarStackView.spacing = 8;
        _navigationBarStackView.axis = UILayoutConstraintAxisHorizontal;
        _navigationBarStackView.alignment = UIStackViewAlignmentCenter;
        [_navigationBarStackView addArrangedSubview:self.doneButton];
        [_navigationBarStackView addArrangedSubview:_timeNavigationTitleView];
        [_navigationBarStackView addArrangedSubview:_rendererButton];
    }
    return _navigationBarStackView;
}

390 391 392
- (void)setupNavigationbar
{

393 394 395 396
    if (!self.timeNavigationTitleView) {
        self.timeNavigationTitleView = [[[NSBundle mainBundle] loadNibNamed:@"VLCTimeNavigationTitleView" owner:self options:nil] objectAtIndex:0];
        self.timeNavigationTitleView.translatesAutoresizingMaskIntoConstraints = NO;
    }
397

398 399 400 401
    if (_vpc.renderer != nil) {
        [_rendererButton setSelected:YES];
    }

402 403
    if (self.navigationBarStackView.superview == nil) {
        [self.navigationController.navigationBar addSubview:self.navigationBarStackView];
404

405

406 407 408 409
        NSObject *guide = self.navigationController.navigationBar;
        if (@available(iOS 11.0, *)) {
            guide = self.navigationController.navigationBar.safeAreaLayoutGuide;
        }
410

411 412 413 414 415 416 417 418 419
        [NSLayoutConstraint activateConstraints:@[
                                                  [NSLayoutConstraint constraintWithItem:self.navigationBarStackView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeCenterY multiplier:1 constant:0],
                                                  [NSLayoutConstraint constraintWithItem:self.navigationBarStackView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeLeft multiplier:1 constant:8],
                                                  [NSLayoutConstraint constraintWithItem:self.navigationBarStackView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeRight multiplier:1 constant:-8],
                                                  [NSLayoutConstraint constraintWithItem:self.navigationBarStackView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeTop multiplier:1 constant:0],
                                                  [NSLayoutConstraint constraintWithItem:self.navigationBarStackView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
                                                  [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.navigationBarStackView attribute:NSLayoutAttributeHeight multiplier:1 constant:0]
                                                  ]];
    }
420
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
421

422 423 424 425 426 427 428 429 430
- (void)resetVideoFiltersSliders
{
    _brightnessSlider.value = 1.;
    _contrastSlider.value = 1.;
    _hueSlider.value = 0.;
    _saturationSlider.value = 1.;
    _gammaSlider.value = 1.;
}

431 432
- (void)viewWillAppear:(BOOL)animated
{
433
    [super viewWillAppear:animated];
434

435
    _vpc.delegate = self;
436
    _lockedOrientation = UIInterfaceOrientationPortrait;
437
    [_vpc recoverPlaybackState];
438 439 440
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
441 442 443 444 445 446
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

447
    [self setControlsHidden:NO animated:animated];
448 449

    [self updateDefaults];
450

451
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
452 453 454 455 456

    VLCRendererDiscovererManager *manager = _services.rendererDiscovererManager;

    manager.presentingViewController = self;
    manager.delegate = self;
457
    if ([_vpc isPlayingOnExternalScreen]) {
458 459
         [self showOnDisplay:_playingExternalView.displayView];
    }
460 461 462 463 464
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
465
    _viewAppeared = YES;
466
    _playbackWillClose = NO;
467

468
    [_vpc recoverDisplayedMetadata];
469
    [self resetVideoFiltersSliders];
470 471
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
472
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
473 474

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
475
    [self setupForMediaProjection];
476 477
}

478
- (void)viewDidLayoutSubviews
479
{
480 481 482 483 484
    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;

485
    CGRect multiSelectionFrame;
486
    CGRect controllerPanelFrame = _controllerPanel.frame;;
487 488 489 490 491 492 493 494 495 496

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

497
    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
498 499 500 501 502 503 504 505 506
        _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;
507
    }
508
    _multiSelectionView.frame = multiSelectionFrame;
509 510
}

511
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
512
{
513 514
    if (_vpc.videoOutputView == self.movieView) {
        _vpc.videoOutputView = nil;
515
    }
516

517
    _viewAppeared = NO;
518
    if (_idleTimer) {
519
        [_idleTimer invalidate];
520 521
        _idleTimer = nil;
    }
522

523
    [super viewWillDisappear:animated];
524

525 526 527 528
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

529 530 531
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

532 533
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
534

535 536
    if (_interfaceIsLocked)
        [self toggleUILock];
537 538 539 540 541
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

542
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
543
    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
544 545
}

546 547 548 549 550 551
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [self.deviceMotion stopDeviceMotion];
}

552 553 554 555 556
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

557 558
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
559
    [self setControlsHidden:YES animated:flag];
560 561 562
    [super dismissViewControllerAnimated:flag completion:completion];
}

563
- (void)updateDefaults
564 565
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
566 567 568 569 570

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
571 572 573 574 575 576 577 578 579

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

580 581
#pragma mark - Initializer helper

582
- (void)sleepTimerInitializer
583 584 585 586
{
    /* add sleep timer UI */
    _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 200.)];
    _sleepTimerContainer.center = self.view.center;
587 588
    _sleepTimerContainer.clipsToBounds = YES;
    _sleepTimerContainer.layer.cornerRadius = 5;
589 590
    _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

591 592 593 594 595 596 597 598 599
    //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;

600
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
601 602
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
603 604 605 606 607
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
608 609 610 611 612
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
613
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_RESET", nil) forState:UIControlStateNormal];
614 615 616 617 618 619 620 621
    [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];
622
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_SET", nil) forState:UIControlStateNormal];
623 624 625 626 627
    [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];
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653

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

654 655
#pragma mark - controls visibility

656 657
- (NSArray *)itemsForInterfaceLock
{
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
    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,
675 676
                                                                     _controllerPanel.volumeView,
                                                                     _rendererButton]];
677 678 679 680 681

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

    return [items copy];
682 683
}

684 685
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
686
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenPixelSize.width);
687

688 689 690
    if ([_vpc currentMediaIs360Video]) {
        [self zoom360Video:diff];
    } else if (recognizer.velocity < 0. && _closeGestureEnabled) {
691
        [self minimizePlayback:nil];
692
    }
693 694
}

695 696 697
- (void)zoom360Video:(CGFloat)zoom
{
    if ([_vpc updateViewpoint:0 pitch:0 roll:0 fov:zoom absolute:NO]) {
698
        //clamp Fov between min and max fov
699 700 701 702
        _fov = MAX(MIN(_fov + zoom, MAX_FOV), MIN_FOV);
    }
}

703 704
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
705
    if (touch.view != self.view)
706 707 708 709 710
        return NO;

    return YES;
}

711 712 713 714 715
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

716
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
717
{
718
    _controlsHidden = hidden;
719
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
720

721
    if (!_controlsHidden) {
722 723
        self.navigationController.navigationBar.alpha = 0.0;
        self.navigationController.navigationBar.hidden = NO;
724
        _controllerPanel.alpha = 0.0f;
725
        _controllerPanel.hidden = !_videoFiltersHidden;
726 727
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
728 729
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
730 731
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
732 733
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
734 735 736 737
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
738 739
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
740 741 742 743

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
744
    }
745

746
    void (^animationBlock)(void) = ^() {
747
        self.navigationController.navigationBar.alpha = alpha;
748 749 750 751 752 753 754 755 756 757 758 759
        self->_controllerPanel.alpha = alpha;
        self->_videoFilterView.alpha = alpha;
        self->_playbackSpeedView.alpha = alpha;
        self->_trackSelectorContainer.alpha = alpha;
        self->_equalizerView.alpha = alpha;
        self->_multiSelectionView.alpha = alpha;
        if (self->_sleepTimerContainer)
            self->_sleepTimerContainer.alpha = alpha;

        CGFloat metaInfoAlpha = self->_audioOnly ? 1.0f : alpha;
        self->_artistNameLabel.alpha = metaInfoAlpha;
        self->_albumNameLabel.alpha = metaInfoAlpha;
760
        self->_trackNameLabel.alpha = [self->_vpc isPlayingOnExternalScreen] ? 0.f : metaInfoAlpha;
761
    };
762

763
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
764 765 766 767 768 769 770 771 772 773 774 775 776
        self->_controllerPanel.hidden = self->_videoFiltersHidden ? self->_controlsHidden : NO;
        self->_videoFilterView.hidden = self->_videoFiltersHidden;
        self->_playbackSpeedView.hidden = self->_playbackSpeedViewHidden;
        self.navigationController.navigationBar.hidden = self->_controlsHidden;
        self->_trackSelectorContainer.hidden = YES;
        self->_equalizerView.hidden = YES;
        if (self->_sleepTimerContainer)
            self->_sleepTimerContainer.hidden = YES;
        self->_multiSelectionView.hidden = YES;

        self->_artistNameLabel.hidden = self->_audioOnly ? NO : self->_controlsHidden;
        self->_albumNameLabel.hidden =  self->_audioOnly ? NO : self->_controlsHidden;
        self->_trackNameLabel.hidden =  self->_audioOnly ? NO : self->_controlsHidden;
777
    };
778

779 780 781 782
    NSTimeInterval animationDuration = animated? 0.3 : 0.0;
    [UIView animateWithDuration:animationDuration animations:^{
        [self setNeedsStatusBarAppearanceUpdate];
    }];
783

784 785
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
786

787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
- (BOOL)prefersStatusBarHidden
{
    return _viewAppeared ? _controlsHidden : NO;
}

- (UIStatusBarStyle)preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}

- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
    return UIStatusBarAnimationFade;
}

802
- (void)toggleControlsVisible
803
{
804 805 806 807 808
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

809 810 811
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

812 813 814
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

815
    [self setControlsHidden:!_controlsHidden animated:YES];
816 817
}

818 819 820 821 822 823 824 825 826
- (void)updateActivityIndicatorForState:(VLCMediaPlayerState)state
{
    if (state == VLCMediaPlayerStatePlaying || state == VLCMediaPlayerStatePaused) {
        _previousPlayerStateWasPaused = state == VLCMediaPlayerStatePaused;
    }
    BOOL shouldAnimate = state == VLCMediaPlayerStateBuffering && !_previousPlayerStateWasPaused;
    if (self.activityIndicator.isAnimating != shouldAnimate) {
        self.activityIndicator.alpha = shouldAnimate ? 1.0 : 0.0;
        shouldAnimate ? [self.activityIndicator startAnimating] : [self.activityIndicator stopAnimating];
827 828 829
    }
}

830
- (void)_resetIdleTimer
831 832
{
    if (!_idleTimer)
833
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
834 835 836 837 838
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
839 840
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
841 842 843
    }
}

844 845 846 847 848 849 850 851 852 853 854 855
- (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];
856

857 858 859
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
860
        [_vpc jumpForward:SHORT_JUMP_DURATION];
861 862 863
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
864
        [_vpc jumpBackward:SHORT_JUMP_DURATION];
865 866 867 868 869 870 871 872 873
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

874 875
- (void)idleTimerExceeded
{
876 877 878 879 880
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

881 882
    _idleTimer = nil;
    if (!_controlsHidden)
883
        [self toggleControlsVisible];
884

885 886 887 888 889
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

890 891 892
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

893 894 895
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

896 897 898
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

899 900
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
901 902
}

903 904
- (UIResponder *)nextResponder
{
905
    [self _resetIdleTimer];
906 907 908
    return [super nextResponder];
}

909
- (VLCDeviceMotion *)deviceMotion
910
{
911 912 913 914 915
    if (!_deviceMotion) {
        _deviceMotion = [VLCDeviceMotion new];
        _deviceMotion.delegate = self;
    }
    return _deviceMotion;
916 917
}

918 919
- (void)setupForMediaProjection
{
920
    BOOL mediaHasProjection = [_vpc currentMediaIs360Video];
921 922 923 924 925 926 927 928 929 930 931 932
    _fov = mediaHasProjection ? DEFAULT_FOV : 0.f;

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

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

933 934 935 936 937 938 939 940 941 942
- (void)applyYaw:(CGFloat)diffYaw pitch:(CGFloat)diffPitch;
{
    //Add and limit new pitch and yaw
    self.deviceMotion.yaw += diffYaw;
    self.deviceMotion.pitch += diffPitch;

    [_vpc updateViewpoint:self.deviceMotion.yaw pitch:self.deviceMotion.pitch roll:0 fov:_fov absolute:YES];
}

- (void)deviceMotionHasAttitudeWithDeviceMotion:(VLCDeviceMotion *)deviceMotion pitch:(double)pitch yaw:(double)yaw
943
{
944
    if (_panRecognizer.state != UIGestureRecognizerStateChanged || UIGestureRecognizerStateBegan) {
945
        [self applyYaw:yaw pitch:pitch];
946
    }
947
}
948 949 950 951
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
952
    _playbackWillClose = YES;
953
    [_vpc stopPlayback];
954
}
955

956 957
- (IBAction)minimizePlayback:(id)sender
{
958
    [_delegate movieViewControllerDidSelectMinimize:self];
959
}
960

961 962
- (IBAction)positionSliderAction:(UISlider *)sender
{
963 964 965
    /* 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. */
966
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
967
    if (_mediaDuration > 0) {
968 969
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
970
        [self.timeNavigationTitleView setNeedsLayout];
971
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
972 973
        _positionSet = NO;
    }
974
    [self _resetIdleTimer];
975 976
}

977 978 979
- (void)_setPositionForReal
{
    if (!_positionSet) {
980
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
981
        [_vpc setNeedsMetadataUpdate];
982 983 984 985
        _positionSet = YES;
    }
}

986 987 988 989
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
990
    _isScrubbing = YES;
991 992 993 994 995
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
996
    _isScrubbing = NO;
997 998 999 1000
}

- (void)_updateScrubLabel
{
1001
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
1002
    if (speed == 1.)
1003
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
1004
    else if (speed == .5)
1005
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
1006
    else if (speed == .25)
1007
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
1008
    else
1009
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
1010 1011

    [self _resetIdleTimer];
1012 1013 1014 1015 1016 1017 1018
}

- (IBAction)positionSliderDrag:(id)sender
{
    [self _updateScrubLabel];
}

1019
- (void)volumeSliderAction:(id)sender
1020 1021 1022 1023
{
    [self _resetIdleTimer];
}

1024 1025 1026 1027
- (void)updateTimeDisplayButton
{
    UIButton *timeDisplayButton = self.timeNavigationTitleView.timeDisplayButton;
    if (_displayRemainingTime)
1028
        [timeDisplayButton setTitle:[[_vpc remainingTime] stringValue] forState:UIControlStateNormal];
1029
    else
1030
        [timeDisplayButton setTitle:[[_vpc playedTime] stringValue] forState:UIControlStateNormal];
1031 1032 1033
    [self.timeNavigationTitleView setNeedsLayout];
}

1034 1035 1036
- (void)updateSleepTimerButton
{
    NSMutableString *title = [NSMutableString stringWithString:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil)];
1037 1038
    if (_vpc.sleepTimer != nil && _vpc.sleepTimer.valid) {
        int remainSeconds = (int)[_vpc.sleepTimer.fireDate timeIntervalSinceNow];
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
        int hour = remainSeconds / 3600;
        int minute = (remainSeconds - hour * 3600) / 60;
        int second = remainSeconds % 60;
        [title appendFormat:@"  %02d:%02d:%02d", hour, minute, second];
    } else {
        [_sleepCountDownTimer invalidate];
    }

    [_sleepTimerButton setTitle:title forState:UIControlStateNormal];
}

1050 1051 1052
#pragma mark - playback controller delegation

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
1053
{
1054 1055 1056
    // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
    [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];

1057
    if (!_isScrubbing) {
1058
        self.timeNavigationTitleView.positionSlider.value = [controller playbackPosition];
1059 1060
    }

1061
    [self updateTimeDisplayButton];
1062 1063
}

1064
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
1065
{
1066
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
1067 1068 1069
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";
1070 1071
    [_equalizerView reloadData];

1072
    double playbackRate = controller.playbackRate;
1073 1074
    self.playbackSpeedSlider.value = log2(playbackRate);
    self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", playbackRate];
1075

1076 1077
    float audioDelay = controller.audioDelay;
    self.audioDelaySlider.value = audioDelay;
1078
    self.audioDelayIndicator.text = [NSString stringWithFormat:@"%d ms", (int) audioDelay];
1079

1080 1081
    float subtitleDelay = controller.subtitleDelay;
    self.spuDelaySlider.value = subtitleDelay;
1082
    self.spuDelayIndicator.text = [NSString stringWithFormat:@"%d ms", (int) subtitleDelay];
1083 1084 1085 1086

    [self _resetIdleTimer];
}

1087
- (void)playbackDidStop:(NSNotification *)notification
1088
{
1089
    [self minimizePlayback:nil];
1090 1091 1092 1093 1094 1095 1096 1097
}

- (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
                      isPlaying:(BOOL)isPlaying
currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
        currentMediaHasChapters:(BOOL)currentMediaHasChapters
          forPlaybackController:(VLCPlaybackController *)controller
{
1098 1099
    [self updateActivityIndicatorForState:currentState];

1100 1101 1102 1103
    if (currentState == VLCMediaPlayerStateBuffering)
        _mediaDuration = controller.mediaDuration;

    if (currentState == VLCMediaPlayerStateError)
1104
        [self.statusLabel showStatusMessage:NSLocalizedString(@"PLAYBACK_FAILED", nil)];
1105

1106
    [_controllerPanel updateButtons];
1107

1108
    _multiSelectionView.mediaHasChapters = currentMediaHasChapters;
1109 1110
}

1111
- (void)showStatusMessage:(NSString *)statusMessage
1112
{
1113
    [self.statusLabel showStatusMessage:statusMessage];
1114 1115
}

1116 1117 1118 1119 1120 1121 1122 1123 1124
- (void)hideShowAspectratioButton:(BOOL)hide
{
    [UIView animateWithDuration:.3
                     animations:^{
                         self.widthConstraint.constant = hide ? 0 : 30;
                         self.timeNavigationTitleView.aspectRatioButton.hidden = hide;
                     }];
}

1125
- (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller metadata:(VLCMetaData *)metadata
1126 1127 1128 1129
{
    if (!_viewAppeared)
        return;

1130 1131 1132 1133 1134
    self.trackNameLabel.text = metadata.title;
    self.artworkImageView.image = metadata.artworkImage;
    if (!metadata.artworkImage) {
        self.artistNameLabel.text = metadata.artist;
        self.albumNameLabel.text = metadata.albumName;
1135 1136
    } else
        self.artistNameLabel.text = self.albumNameLabel.text = nil;
1137

1138
    [self hideShowAspectratioButton:metadata.isAudioOnly];
1139
    [_controllerPanel updateButtons];
1140
    [_playingExternalView updateUIWithRendererItem:_vpc.renderer title:metadata.title];
1141
    
1142
    _audioOnly = metadata.isAudioOnly;
1143 1144
}

1145
- (IBAction)playPause
1146
{
1147
    [_vpc playPause];
1148 1149 1150 1151
}

- (IBAction)forward:(id)sender
{
1152
    [_vpc next];
1153 1154 1155 1156
}

- (IBAction)backward:(id)sender
{
1157
    [_vpc previous];
1158 1159
}

1160
- (IBAction)switchTrack:(id)sender
1161
{
1162 1163
    if (_trackSelectorContainer.hidden == YES || _trackSelectorContainer.switchingTracksNotChapters == NO) {
        _trackSelectorContainer.switchingTracksNotChapters = YES;
1164 1165 1166 1167

        _trackSelectorContainer.hidden = NO;
        _trackSelectorContainer.alpha = 1.;

1168
        [_trackSelectorContainer updateView];
1169

1170 1171 1172
        if (_equalizerView.hidden == NO)
            _equalizerView.hidden = YES;

1173 1174 1175 1176
        if (!_playbackSpeedViewHidden)
            self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;

        self.videoFilterView.hidden = _videoFiltersHidden = YES;
1177 1178 1179 1180 1181

        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:NO];
        [_tapOnVideoRecognizer setEnabled:YES];

1182 1183
    } else {
        _trackSelectorContainer.hidden = YES;
1184
        _trackSelectorContainer.switchingTracksNotChapters = NO;
1185 1186 1187
    }
}

1188 1189 1190
- (IBAction)toggleTimeDisplay:(id)sender
{
    _displayRemainingTime = !_displayRemainingTime;
1191
    [self updateTimeDisplayButton];
1192

1193 1194
    [self _resetIdleTimer];
}
1195

1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
- (IBAction)sleepTimer:(id)sender
{
    if (!_playbackSpeedViewHidden)
        self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;

    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        if (!_controlsHidden) {
            self.controllerPanel.hidden = _controlsHidden = YES;
        }
    }

    self.videoFilterView.hidden = _videoFiltersHidden = YES;
    _sleepTimerContainer.alpha = 1.;
    _sleepTimerContainer.hidden = NO;
}

1215