VLCMovieViewController.m 65.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 "VLCExternalDisplayController.h"
25
#import "VLCEqualizerView.h"
26
#import "VLCMultiSelectionMenuView.h"
27 28
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
29
#import "VLCTimeNavigationTitleView.h"
30 31
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
32
#import "VLCStatusLabel.h"
33
#import "VLCMovieViewControlPanelView.h"
34
#import "VLCSlider.h"
35
#import "VLCLibraryViewController.h"
36
#import "VLCTrackSelectorView.h"
37
#import "VLCMetadata.h"
38
#import "UIDevice+VLC.h"
39
#import "VLC_iOS-Swift.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
40

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

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

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

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

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

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

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

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

98 99
    UIButton *_doneButton;

100
    VLCTrackSelectorView *_trackSelectorContainer;
101 102

    VLCEqualizerView *_equalizerView;
103
    VLCMultiSelectionMenuView *_multiSelectionView;
104

105 106
    VLCPlaybackController *_vpc;

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

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

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

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

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

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

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

143 144
    _vpc = [VLCPlaybackController sharedInstance];

145 146
    self.extendedLayoutIncludesOpaqueBars = YES;
    self.edgesForExtendedLayout = UIRectEdgeAll;
147

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

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

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

177
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
178

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

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

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

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

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

    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGFloat screenScale = [[UIScreen mainScreen] scale];
241
    _screenPixelSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
242 243

    [self setupConstraints];
244

245 246 247 248
}

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

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

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

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

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

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

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

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

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

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

}

- (void)setupNavigationbar
{
363 364 365 366 367 368
    //Needs to be a UIButton since we need it to work with constraints
    _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;
369 370 371 372 373

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

    [self.navigationController.navigationBar addSubview:self.timeNavigationTitleView];
374
    [self.navigationController.navigationBar addSubview:_doneButton];
375

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

390 391 392 393 394 395 396 397 398
- (void)resetVideoFiltersSliders
{
    _brightnessSlider.value = 1.;
    _contrastSlider.value = 1.;
    _hueSlider.value = 0.;
    _saturationSlider.value = 1.;
    _gammaSlider.value = 1.;
}

399 400
- (void)viewWillAppear:(BOOL)animated
{
401
    [super viewWillAppear:animated];
402

403 404
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
405 406 407
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
408 409 410 411 412 413
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

414
    [self setControlsHidden:NO animated:animated];
415 416

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

418
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
419 420 421 422 423
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
424
    _viewAppeared = YES;
425
    _playbackWillClose = NO;
426

427
    [_vpc recoverDisplayedMetadata];
428
    [self resetVideoFiltersSliders];
429 430
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
431
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
Soomin Lee's avatar
Soomin Lee committed
432 433

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
434
    [self setupForMediaProjection];
435 436
}

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

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

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

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

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

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

482
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
483
    [super viewWillDisappear:animated];
484

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

489 490 491
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

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

495 496
    if (_interfaceIsLocked)
        [self toggleUILock];
497 498 499 500 501
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

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

506 507 508 509 510 511
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [self.deviceMotion stopDeviceMotion];
}

512 513 514 515 516
- (BOOL)canBecomeFirstResponder
{
    return YES;
}

517 518
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
519
    [self setControlsHidden:YES animated:flag];
520 521 522
    [super dismissViewControllerAnimated:flag completion:completion];
}

523
- (void)updateDefaults
524 525
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
526 527 528 529 530

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
531 532 533 534 535 536 537 538 539

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

540 541
#pragma mark - Initializer helper

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

551 552 553 554 555 556 557 558 559
    //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;

560
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
561 562
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
563 564 565 566 567
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
568 569 570 571 572
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

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

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

614 615
#pragma mark - controls visibility

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

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

    return [items copy];
641 642
}

643 644
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
645
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenPixelSize.width);
Soomin Lee's avatar
Soomin Lee committed
646

647 648 649
    if ([_vpc currentMediaIs360Video]) {
        [self zoom360Video:diff];
    } else if (recognizer.velocity < 0. && _closeGestureEnabled) {
650
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
651
    }
652 653
}

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

662 663
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
664
    if (touch.view != self.view)
665 666 667 668 669
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
670 671 672 673 674
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

675
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
676
{
677
    _controlsHidden = hidden;
678
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
679

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

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
703
    }
704

705
    void (^animationBlock)() = ^() {
706
        self.navigationController.navigationBar.alpha = alpha;
707
        _controllerPanel.alpha = alpha;
708
        _videoFilterView.alpha = alpha;
709
        _playbackSpeedView.alpha = alpha;
710
        _trackSelectorContainer.alpha = alpha;
711
        _equalizerView.alpha = alpha;
712
        _multiSelectionView.alpha = alpha;
713 714
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
715 716 717 718 719

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
720
    };
721

722
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
723
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
724
        _videoFilterView.hidden = _videoFiltersHidden;
725
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
726
        self.navigationController.navigationBar.hidden = _controlsHidden;
727
        _trackSelectorContainer.hidden = YES;
728
        _equalizerView.hidden = YES;
729 730
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
731
        _multiSelectionView.hidden = YES;
732 733 734 735

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
736
    };
737

738 739
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
740 741

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
742 743
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
744

745
- (void)toggleControlsVisible
746
{
747 748 749 750 751
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

752 753 754
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

755 756 757
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

758
    [self setControlsHidden:!_controlsHidden animated:YES];
759 760
}

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

775 776 777 778 779 780 781 782 783 784 785 786
- (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];
787

788 789 790
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

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

805 806
- (void)idleTimerExceeded
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
807 808 809 810 811
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

812 813
    _idleTimer = nil;
    if (!_controlsHidden)
814
        [self toggleControlsVisible];
815

816 817 818 819 820
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

821 822 823
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

824 825 826
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

827 828 829
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
830 831
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
832 833
}

834 835
- (UIResponder *)nextResponder
{
836
    [self _resetIdleTimer];
837 838 839
    return [super nextResponder];
}

840
- (VLCDeviceMotion *)deviceMotion
Soomin Lee's avatar
Soomin Lee committed
841
{
842 843 844 845 846
    if (!_deviceMotion) {
        _deviceMotion = [VLCDeviceMotion new];
        _deviceMotion.delegate = self;
    }
    return _deviceMotion;
Soomin Lee's avatar
Soomin Lee committed
847 848
}

849 850
- (void)setupForMediaProjection
{
851
    BOOL mediaHasProjection = [_vpc currentMediaIs360Video];
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
    _fov = mediaHasProjection ? DEFAULT_FOV : 0.f;

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

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

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

- (IBAction)closePlayback:(id)sender
{
875
    _playbackWillClose = YES;
876
    [_vpc stopPlayback];
877
}
878

879 880 881
- (IBAction)minimizePlayback:(id)sender
{
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
882
}
883

884 885
- (IBAction)positionSliderAction:(UISlider *)sender
{
886 887 888
    /* 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. */
889
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
890
    if (_mediaDuration > 0) {
891 892
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
893
        [self.timeNavigationTitleView setNeedsLayout];
894
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
895 896
        _positionSet = NO;
    }
897
    [self _resetIdleTimer];
898 899
}

900 901 902
- (void)_setPositionForReal
{
    if (!_positionSet) {
903
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
904
        [_vpc setNeedsMetadataUpdate];
905 906 907 908
        _positionSet = YES;
    }
}

909 910 911 912
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
913
    _isScrubbing = YES;
914 915 916 917 918
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
919
    _isScrubbing = NO;