VLCMovieViewController.m 67 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
};

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
    VLCMediaPlayerState _previousPlayerStateWasPaused;
85

86
    UIPinchGestureRecognizer *_pinchRecognizer;
87
    VLCPanType _currentPanType;
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;
113

114
    VLCDeviceMotion *_deviceMotion;
115 116
    CGFloat _fov;
    CGPoint _saveLocation;
117
    CGSize _screenPixelSize;
118
    UIInterfaceOrientation _lockedOrientation;
119 120 121

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

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

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

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

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

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

148 149
    _vpc = [VLCPlaybackController sharedInstance];

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

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

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

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

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

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

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

    [self setupConstraints];
243
    [self setupRendererDiscovererManager];
244 245 246 247
}

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

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

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

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

284
    [self.view addGestureRecognizer:_pinchRecognizer];
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 293
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
294 295
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
296 297 298 299

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

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

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

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

}

- (void)setupNavigationbar
{
362 363 364 365 366
    _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;
367 368 369 370

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

371 372 373 374 375 376 377 378 379 380 381 382 383 384
    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];
385

386 387
    NSObject *guide = self.navigationController.navigationBar;
    if (@available(iOS 11.0, *)) {
388
        guide = self.navigationController.navigationBar.layoutMarginsGuide;
389
    }
390 391 392 393 394 395 396 397 398

    [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]
                                              ]];
399
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
400

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

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

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

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

    [self updateDefaults];
429

430
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
431 432 433 434

    VLCRendererDiscovererManager *manager = [VLCRendererDiscovererManager sharedInstance];
    manager.presentingViewController = self;
    manager.delegate = self;
435 436
    if ([_vpc isPlayingOnExternalScreen]) {
         [self showOnDisplay:_playingExternalView.displayView];
437
    }
438 439 440 441 442
}

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

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

    //Media is loaded in the media player, checking the projection type and configuring accordingly.
453
    [self setupForMediaProjection];
454
    [VLCRendererDiscovererManager.sharedInstance start];
455 456
}

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

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

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

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

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

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

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

505 506 507 508
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

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

512 513
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
514

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

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

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

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

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

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

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

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

560 561
#pragma mark - Initializer helper

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

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

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

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

    UIButton *cancelSleepTimer = [[UIButton alloc] initWithFrame:CGRectMake(0., 162., 150., 48.)];
    cancelSleepTimer.backgroundColor = [UIColor clearColor];
593
    [cancelSleepTimer setTitle:NSLocalizedString(@"BUTTON_RESET", nil) forState:UIControlStateNormal];
594 595 596 597 598 599 600 601
    [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];
602
    [confirmSleepTimer setTitle:NSLocalizedString(@"BUTTON_SET", nil) forState:UIControlStateNormal];
603 604 605 606 607
    [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];
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 633

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

634 635
#pragma mark - controls visibility

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

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

    return [items copy];
662 663
}

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

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

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

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

    return YES;
}

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

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

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

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

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

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
740
        _trackNameLabel.alpha = [_vpc isPlayingOnExternalScreen] ? 0.f : metaInfoAlpha;
741
    };
742

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

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

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

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

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

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

776 777 778
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

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

782 783 784 785 786 787 788 789 790
- (void)updateActivityIndicatorForState:(VLCMediaPlayerState)state
{
    if (state == VLCMediaPlayerStatePlaying || state == VLCMediaPlayerStatePaused) {
        _previousPlayerStateWasPaused = state == VLCMediaPlayerStatePaused;
    }
    BOOL shouldAnimate = state == VLCMediaPlayerStateBuffering && !_previousPlayerStateWasPaused;
    if (self.activityIndicator.isAnimating != shouldAnimate) {
        self.activityIndicator.alpha = shouldAnimate ? 1.0 : 0.0;
        shouldAnimate ? [self.activityIndicator startAnimating] : [self.activityIndicator stopAnimating];
791 792 793
    }
}

794
- (void)_resetIdleTimer
795 796
{
    if (!_idleTimer)
797
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
798 799 800 801 802
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
803 804
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
805 806 807
    }
}

808 809 810 811 812 813 814 815 816 817 818 819
- (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];
820

821 822 823
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
824
        [_vpc jumpForward:SHORT_JUMP_DURATION];
825 826 827
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
828
        [_vpc jumpBackward:SHORT_JUMP_DURATION];
829 830 831 832 833 834 835 836 837
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

838 839
- (void)idleTimerExceeded
{
840 841 842 843 844
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

845 846
    _idleTimer = nil;
    if (!_controlsHidden)
847
        [self toggleControlsVisible];
848

849 850 851 852 853
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

854 855 856
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

857 858 859
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

860 861 862
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

863 864
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
865 866
}

867 868
- (UIResponder *)nextResponder
{
869
    [self _resetIdleTimer];
870 871 872
    return [super nextResponder];
}

873
- (VLCDeviceMotion *)deviceMotion
874
{
875 876 877 878 879
    if (!_deviceMotion) {
        _deviceMotion = [VLCDeviceMotion new];
        _deviceMotion.delegate = self;
    }
    return _deviceMotion;
880 881
}

882 883
- (void)setupForMediaProjection
{
884
    BOOL mediaHasProjection = [_vpc currentMediaIs360Video];
885 886 887 888 889 890 891 892 893 894 895 896
    _fov = mediaHasProjection ? DEFAULT_FOV : 0.f;

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

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

897 898 899 900 901 902 903 904 905 906
- (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
907
{
908
    if (_panRecognizer.state != UIGestureRecognizerStateChanged || UIGestureRecognizerStateBegan) {
909
        [self applyYaw:yaw pitch:pitch];
910
    }
911
}
912 913 914 915
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
916
    _playbackWillClose = YES;
917
    [_vpc stopPlayback];
918
}
919

920 921 922
- (IBAction)minimizePlayback:(id)sender
{
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
923
}
924

925 926
- (IBAction)positionSliderAction:(UISlider *)sender
{
927 928 929
    /* 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. */
930
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
931
    if (_mediaDuration > 0) {
932 933
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
934
        [self.timeNavigationTitleView setNeedsLayout];
935
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
936 937
        _positionSet = NO;
    }
938
    [self _resetIdleTimer];
939 940
}

941 942 943
- (void)_setPositionForReal
{
    if (!_positionSet) {
944
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
945
        [_vpc setNeedsMetadataUpdate];
946 947 948 949
        _positionSet = YES;
    }
}

950 951 952 953
- (IBAction)positionSliderTouchDown:(id)sender
{
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
954
    _isScrubbing = YES;
955 956 957 958 959
}

- (IBAction)positionSliderTouchUp:(id)sender
{
    self.scrubIndicatorView.hidden = YES;
960
    _isScrubbing = NO;
961 962 963 964
}

- (void)_updateScrubLabel
{
965
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
966
    if (speed == 1.)
967
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
968
    else if (speed == .5)
969
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
970
    else if (speed == .25)
971
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
972
    else
973
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
974 975

    [self _resetIdleTimer];
976 977 978 979 980 981 982
}

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

983
- (void)volumeSliderAction:(id)sender
984 985 986 987
{
    [self _resetIdleTimer];
}

988 989 990 991
- (void)updateTimeDisplayButton
{
    UIButton *timeDisplayButton = self.timeNavigationTitleView.timeDisplayButton;
    if (_displayRemainingTime)
992
        [timeDisplayButton setTitle:[[_vpc remainingTime] stringValue] forState:UIControlStateNormal];
993
    else
994
        [timeDisplayButton setTitle:[[_vpc playedTime] stringValue] forState:UIControlStateNormal];
995 996 997
    [self.timeNavigationTitleView setNeedsLayout];
}

998 999 1000
- (void)updateSleepTimerButton
{
    NSMutableString *title = [NSMutableString stringWithString:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil)];
1001 1002
    if (_vpc.sleepTimer != nil && _vpc.sleepTimer.valid) {
        int remainSeconds = (int)[_vpc.sleepTimer.fireDate timeIntervalSinceNow];
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
        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];
}

1014 1015 1016
#pragma mark - playback controller delegation

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
1017
{
1018 1019 1020
    // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
    [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];

1021
    if (!_isScrubbing) {
1022
        self.timeNavigationTitleView.positionSlider.value = [controller playbackPosition];
1023 1024
    }

1025
    [self updateTimeDisplayButton];
1026 1027
}

1028
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
1029
{
1030
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
1031 1032 1033
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";
1034 1035
    [_equalizerView reloadData];

1036
    double playbackRate = controller.playbackRate;
1037 1038
    self.playbackSpeedSlider.value = log2(playbackRate);
    self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", playbackRate];
1039

1040 1041
    float audioDelay = controller.audioDelay;
    self.audioDelaySlider.value = audioDelay;
1042
    self.audioDelayIndicator.text = [NSString stringWithFormat:@"%d ms", (int) audioDelay];
1043

1044 1045
    float subtitleDelay = controller.subtitleDelay;
    self.spuDelaySlider.value = subtitleDelay;
1046
    self.spuDelayIndicator.text = [NSString stringWithFormat:@"%d ms", (int) subtitleDelay];
1047 1048 1049 1050

    [self _resetIdleTimer];
}

1051
- (void)playbackDidStop:(NSNotification *)notification
1052
{
1053
    [self minimizePlayback:nil];
1054 1055 1056 1057 1058 1059 1060 1061
}

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

1064 1065 1066 1067
    if (currentState == VLCMediaPlayerStateBuffering)
        _mediaDuration = controller.mediaDuration;

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

1070
    [_controllerPanel updateButtons];
1071

1072
    _multiSelectionView.mediaHasChapters = currentMediaHasChapters;
1073 1074 1075 1076
}

- (void)showStatusMessage:(NSString *)statusMessage forPlaybackController:(VLCPlaybackController *)controller
{
1077
    [self.statusLabel showStatusMessage:statusMessage];
1078 1079
}

1080 1081 1082 1083 1084 1085 1086 1087 1088
- (void)hideShowAspectratioButton:(BOOL)hide
{
    [UIView animateWithDuration:.3
                     animations:^{
                         self.widthConstraint.constant = hide ? 0 : 30;
                         self.timeNavigationTitleView.aspectRatioButton.hidden = hide;
                     }];
}

1089
- (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller metadata:(VLCMetaData *)metadata
1090 1091 1092 1093
{
    if (!_viewAppeared)
        return;

1094 1095 1096 1097 1098
    self.trackNameLabel.text = metadata.title;
    self.artworkImageView.image = metadata.artworkImage;
    if (!metadata.artworkImage) {
        self.artistNameLabel.text = metadata.artist;
        self.albumNameLabel.text = metadata.albumName;
1099 1100
    } else
        self.artistNameLabel.text = self.albumNameLabel.text = nil;
1101

1102
    [self hideShowAspectratioButton:metadata.isAudioOnly];
1103
    [_controllerPanel updateButtons];
1104
    [_playingExternalView updateUIWithRendererItem:_vpc.renderer title:metadata.title];
1105
    
1106
    _audioOnly = metadata.isAudioOnly;
1107 1108
}

1109
- (IBAction)playPause
1110
{
1111
    [_vpc playPause];
1112 1113 1114 1115
}

- (IBAction)forward:(id)sender
{
1116
    [_vpc next];
1117 1118 1119 1120
}

- (IBAction)backward:(id)sender
{
1121
    [_vpc previous];
1122 1123
}

1124
- (IBAction)switchTrack:(id)sender
1125
{
1126 1127
    if (_trackSelectorContainer.hidden == YES || _trackSelectorContainer.switchingTracksNotChapters == NO) {
        _trackSelectorContainer.switchingTracksNotChapters = YES;
1128 1129 1130 1131

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

1132
        [_trackSelectorContainer updateView];
1133

1134 1135 1136
        if (_equalizerView.hidden == NO)
            _equalizerView.hidden = YES;

1137 1138 1139 1140
        if (!_playbackSpeedViewHidden)
            self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;

        self.videoFilterView.hidden = _videoFiltersHidden = YES;
1141 1142 1143 1144 1145

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

1146 1147
    } else {
        _trackSelectorContainer.hidden = YES;
1148
        _trackSelectorContainer.switchingTracksNotChapters = NO;
1149 1150 1151
    }
}

1152 1153 1154
- (IBAction)toggleTimeDisplay:(id)sender
{
    _displayRemainingTime = !_displayRemainingTime;
1155
    [self updateTimeDisplayButton];
1156

1157 1158
    [self _resetIdleTimer];
}
1159

1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
- (IBAction)sleepTimer:(id)sender
{
    if (!_playbackSpeedViewHidden)
        self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;

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

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

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

1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
- (IBAction)sleepTimerCancel:(id)sender
{
    NSTimer *sleepTimer = [_vpc sleepTimer];

    if (sleepTimer) {
        [sleepTimer invalidate];
        sleepTimer = nil;
    }
    [self.statusLabel showStatusMessage:NSLocalizedString(@"SLEEP_TIMER_UPDATED", nil)];
    [self setControlsHidden:YES animated:YES];
}

1191 1192
- (IBAction)sleepTimerAction:(id)sender
{