VLCMovieViewController.m 62.9 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"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
37

38 39
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
40
#define SHORT_JUMP_DURATION 10
41

Soomin Lee's avatar
Soomin Lee committed
42 43 44 45 46
#define ZOOM_SENSITIVITY 2.99f
#define DEFAULT_FOV 80.f
#define MAX_FOV 150.f
#define MIN_FOV 20.f

47 48 49 50
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

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

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

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

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

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

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

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 115 116

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

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

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

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

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

142 143
    int deviceSpeedCategory = [[UIDevice currentDevice] VLCSpeedCategory];

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

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

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

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

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

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

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

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

203
    _movieView.userInteractionEnabled = NO;
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

    [self setupGestureRecognizers];

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

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

    _playerIsSetup = NO;

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

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

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

    //Sleep Timer initialization
    [self sleepTimerInitializer:deviceSpeedCategory];
    [self setupControlPanel];

    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGFloat screenScale = [[UIScreen mainScreen] scale];
    _screenSizePixel = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
    _saveLocation = CGPointMake(-1.f, -1.f);

    [self setupConstraints];
248

249 250 251 252
}

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

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

260 261 262 263 264 265
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:_pinchRecognizer];

    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
    [_tapRecognizer setNumberOfTouchesRequired:2];
266 267

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

272 273
    [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];

274 275 276 277
    _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 286

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

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 348 349 350 351 352 353 354 355

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

}

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

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

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

    [NSLayoutConstraint activateConstraints: @[
                                               [NSLayoutConstraint constraintWithItem:doneButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar 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],
                                               [NSLayoutConstraint constraintWithItem:self.timeNavigationTitleView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeRight multiplier:1 constant:0],
                                               [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],
                                               ]];
380
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
381

382 383
- (void)viewWillAppear:(BOOL)animated
{
384
    [super viewWillAppear:animated];
385

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

395 396 397
    _vpc = [VLCPlaybackController sharedInstance];
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
398

399
    [self screenBrightnessChanged:nil];
400
    [self setControlsHidden:NO animated:animated];
401 402

    [self updateDefaults];
Soomin Lee's avatar
Soomin Lee committed
403 404 405

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

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
412
    _viewAppeared = YES;
413
    _playbackWillClose = NO;
414

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

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
    _fov = 0.f;
    _mediaHasProjection = NO;
    if ([_vpc currentMediaProjection] == VLCMediaProjectionEquiRectangular) {
        _fov = DEFAULT_FOV;
        _mediaHasProjection = YES;
    }

    [self enableNormalVideoGestures:!_mediaHasProjection];
430

431 432
}

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

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

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

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

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

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

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

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

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

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

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

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

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

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

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

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

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

528 529 530 531 532 533 534
#pragma mark - Initializer helper

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

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

548 549 550 551 552 553 554 555 556 557 558
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
    if (deviceSpeedCategory >= 3) {
        _sleepTimeDatePicker.opaque = NO;
        _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
    } else
        _sleepTimeDatePicker.backgroundColor = [UIColor blackColor];
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    [_sleepTimerContainer.layer addSublayer:horizontalSeparator];
    [_sleepTimerContainer.layer addSublayer:verticalSeparator];

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) forState:UIControlStateNormal];
    [cancelSleepTimer setTintColor:[UIColor VLCLightTextColor]];
    [cancelSleepTimer setTitleColor:[UIColor VLCDarkTextShadowColor] forState:UIControlStateHighlighted];
    [cancelSleepTimer addTarget:self action:@selector(sleepTimerCancel:) forControlEvents:UIControlEventTouchDown];
    cancelSleepTimer.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 10, 0);
    [_sleepTimerContainer addSubview:cancelSleepTimer];

    UIButton *confirmSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(150., 162., 150., 48.)];
    confirmSleepTimer.backgroundColor = [UIColor clearColor];
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_DONE", nil) forState:UIControlStateNormal];
    [confirmSleepTimer setTintColor:[UIColor VLCLightTextColor]];
    [confirmSleepTimer setTitleColor:[UIColor VLCDarkTextShadowColor] forState:UIControlStateHighlighted];
    [confirmSleepTimer addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventTouchDown];
    confirmSleepTimer.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 10, 0);
    [_sleepTimerContainer addSubview:confirmSleepTimer];
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

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

605 606
#pragma mark - controls visibility

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

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

Soomin Lee's avatar
Soomin Lee committed
614 615 616 617 618 619 620 621
    CGFloat diff = DEFAULT_FOV * -(ZOOM_SENSITIVITY * recognizer.velocity / _screenSizePixel.width);

    if (_mediaHasProjection) {
        if ([_vpc updateViewpoint:0 pitch:0 roll:0 fov:diff absolute:NO]) {
            //Checking for fov value in case of
            _fov = MAX(MIN(_fov + diff, MAX_FOV), MIN_FOV);
        }
    } else if (recognizer.velocity < 0.) {
622
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
623
    }
624 625
}

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

    return YES;
}

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

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

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

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

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

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

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

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

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

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

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

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

719 720 721
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

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

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

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
- (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];
    VLCMediaPlayer *mediaPlayer = _vpc.mediaPlayer;
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
        [mediaPlayer shortJumpForward];
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
        [mediaPlayer shortJumpBackward];
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

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

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

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

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

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

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

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

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

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

813 814 815 816
#pragma mark - controls

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

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

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

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

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

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

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

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

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

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

    [self _resetIdleTimer];
885 886 887 888
}

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

891 892 893
    [self _updateScrubLabel];
}

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

898 899 900
    [self _resetIdleTimer];
}

901 902
- (void)updateTimeDisplayButton
{
903
    VLCMediaPlayer *mediaPlayer = _vpc.mediaPlayer;
904 905 906 907 908 909 910 911
    UIButton *timeDisplayButton = self.timeNavigationTitleView.timeDisplayButton;
    if (_displayRemainingTime)
        [timeDisplayButton setTitle:[[mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
    else
        [timeDisplayButton setTitle:[[mediaPlayer time] stringValue] forState:UIControlStateNormal];
    [self.timeNavigationTitleView setNeedsLayout];
}

912 913 914
- (void)updateSleepTimerButton
{
    NSMutableString *title = [NSMutableString stringWithString:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil)];
915 916
    if (_vpc.sleepTimer != nil && _vpc.sleepTimer.valid) {
        int remainSeconds = (int)[_vpc.sleepTimer.fireDate timeIntervalSinceNow];
917 918 919 920 921 922 923 924 925 926 927
        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];
}

928 929 930
#pragma mark - playback controller delegation

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
931
{
932
    VLCMediaPlayer *mediaPlayer = controller.mediaPlayer;