VLCMovieViewController.m 66.1 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 30
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
31
#import "VLCStatusLabel.h"
32
#import "VLCMovieViewControlPanelView.h"
33
#import "VLCSlider.h"
34
#import "VLCLibraryViewController.h"
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
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,
54
  VLCPanTypeProjection
55 56
};

Soomin Lee's avatar
Soomin Lee committed
57
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate, VLCPlaybackControllerDelegate, VLCDeviceMotionDelegate, VLCRendererDiscovererManagerDelegate>
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;
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;
84

85
    UIPinchGestureRecognizer *_pinchRecognizer;
86
    VLCPanType _currentPanType;
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;
112

113
    VLCDeviceMotion *_deviceMotion;
114 115
    CGFloat _fov;
    CGPoint _saveLocation;
116
    CGSize _screenPixelSize;
117
    UIInterfaceOrientation _lockedOrientation;
Soomin Lee's avatar
Soomin Lee committed
118 119 120

    UIStackView *_navigationBarStackView;
    UIButton *_rendererButton;
121
}
122
@property (nonatomic, strong) VLCMovieViewControlPanelView *controllerPanel;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
123
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
124 125
@property (nonatomic, strong) IBOutlet VLCPlayingExternallyView *playingExternalView;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
126 127
@end

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

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

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

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

147 148
    _vpc = [VLCPlaybackController sharedInstance];

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

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

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

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

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

183 184
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
185 186 187 188 189
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
190 191 192 193
    [center addObserver:self
               selector:@selector(appBecameActive:)
                   name:UIApplicationDidBecomeActiveNotification
                 object:nil];
194 195 196 197
    [center addObserver:self
               selector:@selector(playbackDidStop:)
                   name:VLCPlaybackControllerPlaybackDidStop
                 object:nil];
198

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

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

    [self setupGestureRecognizers];

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

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

    [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
235
    [self sleepTimerInitializer];
236 237 238
    [self setupControlPanel];

    CGRect screenBounds = [[UIScreen mainScreen] bounds];
239
    _screenPixelSize = CGSizeMake(screenBounds.size.width, screenBounds.size.height);
240 241

    [self setupConstraints];
Soomin Lee's avatar
Soomin Lee committed
242
    [self setupRendererDiscovererManager];
243 244 245 246
}

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

251 252 253
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;

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

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

283
    [self.view addGestureRecognizer:_pinchRecognizer];
284 285
    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
286 287
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
288 289
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
290

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

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
299 300
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
301
    _tapRecognizer.delegate = self;
302
    _tapToSeekRecognizer.delegate = self;
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 329 330 331 332
}

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

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

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

}

- (void)setupNavigationbar
{
361 362 363 364 365
    _doneButton = [[UIButton alloc] initWithFrame:CGRectZero];
    [_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;
366 367 368 369

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

Soomin Lee's avatar
Soomin Lee committed
370 371 372 373 374 375 376 377 378 379 380 381 382 383
    if (_vpc.renderer != nil) {
        [_rendererButton setSelected:YES];
    }

    _navigationBarStackView = [[UIStackView alloc] init];
    _navigationBarStackView.translatesAutoresizingMaskIntoConstraints = NO;
    _navigationBarStackView.spacing = 8;
    _navigationBarStackView.axis = UILayoutConstraintAxisHorizontal;
    _navigationBarStackView.alignment = UIStackViewAlignmentCenter;
    [_navigationBarStackView addArrangedSubview:_doneButton];
    [_navigationBarStackView addArrangedSubview:_timeNavigationTitleView];
    [_navigationBarStackView addArrangedSubview:_rendererButton];

    [self.navigationController.navigationBar addSubview:_navigationBarStackView];
384

385 386 387 388
    NSObject *guide = self.navigationController.navigationBar;
    if (@available(iOS 11.0, *)) {
        guide = self.navigationController.navigationBar.safeAreaLayoutGuide;
    }
Soomin Lee's avatar
Soomin Lee committed
389 390 391 392 393 394 395 396 397

    [NSLayoutConstraint activateConstraints:@[
                                              [NSLayoutConstraint constraintWithItem:_navigationBarStackView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeCenterY multiplier:1 constant:0],
                                              [NSLayoutConstraint constraintWithItem:_navigationBarStackView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeLeft multiplier:1 constant:8],
                                              [NSLayoutConstraint constraintWithItem:_navigationBarStackView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:guide attribute:NSLayoutAttributeRight multiplier:1 constant:-8],
                                              [NSLayoutConstraint constraintWithItem:_navigationBarStackView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeTop multiplier:1 constant:0],
                                              [NSLayoutConstraint constraintWithItem:_navigationBarStackView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
                                              [NSLayoutConstraint constraintWithItem:_timeNavigationTitleView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_navigationBarStackView attribute:NSLayoutAttributeHeight multiplier:1 constant:0]
                                              ]];
398
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
399

400 401 402 403 404 405 406 407 408
- (void)resetVideoFiltersSliders
{
    _brightnessSlider.value = 1.;
    _contrastSlider.value = 1.;
    _hueSlider.value = 0.;
    _saturationSlider.value = 1.;
    _gammaSlider.value = 1.;
}

409 410
- (void)viewWillAppear:(BOOL)animated
{
411
    [super viewWillAppear:animated];
412

413
    _vpc.delegate = self;
414
    _lockedOrientation = UIInterfaceOrientationPortrait;
415
    [_vpc recoverPlaybackState];
416 417 418
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
419 420 421 422 423 424
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

425
    [self setControlsHidden:NO animated:animated];
426 427

    [self updateDefaults];
428

429
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
Soomin Lee's avatar
Soomin Lee committed
430 431 432 433

    VLCRendererDiscovererManager *manager = [VLCRendererDiscovererManager sharedInstance];
    manager.presentingViewController = self;
    manager.delegate = self;
434 435 436
    if (_vpc.renderer || [[UIDevice currentDevice] VLCHasExternalDisplay]) {
         [self showOnDisplay:_playingExternalView.displayView];
    }
437 438 439 440 441
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
442
    _viewAppeared = YES;
443
    _playbackWillClose = NO;
444

445
    [_vpc recoverDisplayedMetadata];
446
    [self resetVideoFiltersSliders];
447 448
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
449
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
450 451

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
452
    [self setupForMediaProjection];
Soomin Lee's avatar
Soomin Lee committed
453
    [VLCRendererDiscovererManager.sharedInstance start];
454 455
}

456
- (void)viewDidLayoutSubviews
457
{
458 459 460 461 462
    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;

463
    CGRect multiSelectionFrame;
464
    CGRect controllerPanelFrame = _controllerPanel.frame;;
465 466 467 468 469 470 471 472 473 474

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

475
    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
476 477 478 479 480 481 482 483 484
        _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;
485
    }
486
    _multiSelectionView.frame = multiSelectionFrame;
487 488
}

489
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
490
{
491 492
    if (_vpc.videoOutputView == self.movieView) {
        _vpc.videoOutputView = nil;
493
    }
494

495
    _viewAppeared = NO;
496
    if (_idleTimer) {
497
        [_idleTimer invalidate];
498 499
        _idleTimer = nil;
    }
500

501
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
502
    [super viewWillDisappear:animated];
503

Felix Paul Kühne's avatar
Felix Paul Kühne committed
504 505 506 507
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

508 509 510
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
511 512
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
513

514 515
    if (_interfaceIsLocked)
        [self toggleUILock];
516 517 518 519 520
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

521
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
522
    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
523 524
}

525 526 527 528 529 530
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [self.deviceMotion stopDeviceMotion];
}

531 532 533 534 535
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

536 537
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
538
    [self setControlsHidden:YES animated:flag];
539 540 541
    [super dismissViewControllerAnimated:flag completion:completion];
}

542
- (void)updateDefaults
543 544
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
545 546 547 548 549

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
550 551 552 553 554 555 556 557 558

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

559 560
#pragma mark - Initializer helper

561
- (void)sleepTimerInitializer
562 563 564 565
{
    /* add sleep timer UI */
    _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 200.)];
    _sleepTimerContainer.center = self.view.center;
566 567
    _sleepTimerContainer.clipsToBounds = YES;
    _sleepTimerContainer.layer.cornerRadius = 5;
568 569
    _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

570 571 572 573 574 575 576 577 578
    //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;

579
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
580 581
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
582 583 584 585 586
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
587 588 589 590 591
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
592
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_RESET", nil) forState:UIControlStateNormal];
593 594 595 596 597 598 599 600
    [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];
601
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_SET", nil) forState:UIControlStateNormal];
602 603 604 605 606
    [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];
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632

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

633 634
#pragma mark - controls visibility

635 636
- (NSArray *)itemsForInterfaceLock
{
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
    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,
Soomin Lee's avatar
Soomin Lee committed
654 655
                                                                     _controllerPanel.volumeView,
                                                                     _rendererButton]];
656 657 658 659 660

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

    return [items copy];
661 662
}

663 664
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
665
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenPixelSize.width);
666

667 668 669
    if ([_vpc currentMediaIs360Video]) {
        [self zoom360Video:diff];
    } else if (recognizer.velocity < 0. && _closeGestureEnabled) {
670
        [self minimizePlayback:nil];
671
    }
672 673
}

674 675 676
- (void)zoom360Video:(CGFloat)zoom
{
    if ([_vpc updateViewpoint:0 pitch:0 roll:0 fov:zoom absolute:NO]) {
677
        //clamp Fov between min and max fov
678 679 680 681
        _fov = MAX(MIN(_fov + zoom, MAX_FOV), MIN_FOV);
    }
}

682 683
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
684
    if (touch.view != self.view)
685 686 687 688 689
        return NO;

    return YES;
}

690 691 692 693 694
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

695
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
696
{
697
    _controlsHidden = hidden;
698
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
699

700
    if (!_controlsHidden) {
701 702
        self.navigationController.navigationBar.alpha = 0.0;
        self.navigationController.navigationBar.hidden = NO;
703
        _controllerPanel.alpha = 0.0f;
704
        _controllerPanel.hidden = !_videoFiltersHidden;
705 706
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
707 708
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
709 710
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
711 712
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
713 714 715 716
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
717 718
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
719 720 721 722

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
723
    }
724

725
    void (^animationBlock)() = ^() {
726
        self.navigationController.navigationBar.alpha = alpha;
727
        _controllerPanel.alpha = alpha;
728
        _videoFilterView.alpha = alpha;
729
        _playbackSpeedView.alpha = alpha;
730
        _trackSelectorContainer.alpha = alpha;
731
        _equalizerView.alpha = alpha;
732
        _multiSelectionView.alpha = alpha;
733 734
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
735 736 737 738 739

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
740
    };
741

742
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
743
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
744
        _videoFilterView.hidden = _videoFiltersHidden;
745
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
746
        self.navigationController.navigationBar.hidden = _controlsHidden;
747
        _trackSelectorContainer.hidden = YES;
748
        _equalizerView.hidden = YES;
749 750
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
751
        _multiSelectionView.hidden = YES;
752 753 754 755

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
756
    };
757

758 759
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
760 761

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
762 763
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
764

765
- (void)toggleControlsVisible
766
{
767 768 769 770 771
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

772 773 774
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

775 776 777
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

778
    [self setControlsHidden:!_controlsHidden animated:YES];
779 780
}

781
- (void)_resetIdleTimer
782 783
{
    if (!_idleTimer)
784
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
785 786 787 788 789
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
790 791
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
792 793 794
    }
}

795 796 797 798 799 800 801 802 803 804 805 806
- (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];
807

808 809 810
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
811
        [_vpc jumpForward:SHORT_JUMP_DURATION];
812 813 814
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
815
        [_vpc jumpBackward:SHORT_JUMP_DURATION];
816 817 818 819 820 821 822 823 824
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

825 826
- (void)idleTimerExceeded
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
827 828 829 830 831
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

832 833
    _idleTimer = nil;
    if (!_controlsHidden)
834
        [self toggleControlsVisible];
835

836 837 838 839 840
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

841 842 843
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

844 845 846
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

847 848 849
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
850 851
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
852 853
}

854 855
- (UIResponder *)nextResponder
{
856
    [self _resetIdleTimer];
857 858 859
    return [super nextResponder];
}

860
- (VLCDeviceMotion *)deviceMotion
861
{
862 863 864 865 866
    if (!_deviceMotion) {
        _deviceMotion = [VLCDeviceMotion new];
        _deviceMotion.delegate = self;
    }
    return _deviceMotion;
867 868
}

869 870
- (void)setupForMediaProjection
{
871
    BOOL mediaHasProjection = [_vpc currentMediaIs360Video];
872 873 874 875 876 877 878 879 880 881 882 883
    _fov = mediaHasProjection ? DEFAULT_FOV : 0.f;

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

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

884 885 886 887 888 889 890 891 892 893
- (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
894
{
895
    if (_panRecognizer.state != UIGestureRecognizerStateChanged || UIGestureRecognizerStateBegan) {
896
        [self applyYaw:yaw pitch:pitch];
897
    }
898
}
899 900 901 902
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
903
    _playbackWillClose = YES;
904
    [_vpc stopPlayback];
905
}
906

907 908 909
- (IBAction)minimizePlayback:(id)sender
{
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
910
}
911

912 913
- (IBAction)positionSliderAction:(UISlider *)sender
{
914 915 916
    /* 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. */
917
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
918
    if (_mediaDuration > 0) {
919 920
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
921
        [self.timeNavigationTitleView setNeedsLayout];
922
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
923 924
        _positionSet = NO;
    }
925
    [self _resetIdleTimer];
926 927
}

928 929 930
- (void)_setPositionForReal
{
    if (!_positionSet) {
931
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
932
        [_vpc setNeedsMetadataUpdate];
933 934 935 936
        _positionSet = YES;
    }
}

937 938 939 940
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
941
    _isScrubbing = YES;
942 943 944 945 946
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
947
    _isScrubbing = NO;
948 949 950 951
}

- (void)_updateScrubLabel
{
952
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
953
    if (speed == 1.)
954
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
955
    else if (speed == .5)
956
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
957
    else if (speed == .25)
958
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
959
    else
960
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
961 962

    [self _resetIdleTimer];
963 964 965 966 967 968 969
}

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

970
- (void)volumeSliderAction:(id)sender
971 972 973 974
{
    [self _resetIdleTimer];
}

975 976 977 978
- (void)updateTimeDisplayButton
{
    UIButton *timeDisplayButton = self.timeNavigationTitleView.timeDisplayButton;
    if (_displayRemainingTime)
979
        [timeDisplayButton setTitle:[[_vpc remainingTime] stringValue] forState:UIControlStateNormal];
980
    else
981
        [timeDisplayButton setTitle:[[_vpc playedTime] stringValue] forState:UIControlStateNormal];
982 983 984
    [self.timeNavigationTitleView setNeedsLayout];
}

985 986 987
- (void)updateSleepTimerButton
{
    NSMutableString *title = [NSMutableString stringWithString:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil)];
988 989
    if (_vpc.sleepTimer != nil && _vpc.sleepTimer.valid) {
        int remainSeconds = (int)[_vpc.sleepTimer.fireDate timeIntervalSinceNow];
990 991 992 993 994 995 996 997 998 999 1000
        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];
}

1001 1002 1003
#pragma mark - playback controller delegation

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
1004
{
1005
    if (!_isScrubbing) {
1006
        self.timeNavigationTitleView.positionSlider.value = [controller playbackPosition];
1007 1008
    }

1009
    [self updateTimeDisplayButton];
1010 1011
}

1012
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
1013
{
1014
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
1015 1016 1017
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";
1018 1019
    [_equalizerView reloadData];

1020
    double playbackRate = controller.playbackRate;
1021 1022
    self.playbackSpeedSlider.value = log2(playbackRate);
    self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", playbackRate];
1023

1024 1025
    float audioDelay = controller.audioDelay;
    self.audioDelaySlider.value = audioDelay;
1026
    self.audioDelayIndicator.text = [NSString stringWithFormat:@"%d ms", (int) audioDelay];