VLCMovieViewController.m 62.1 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2017 VideoLAN. All rights reserved.
6 7 8 9
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
10 11
 *          Carola Nitz <caro # videolan.org>
 *          Tobias Conradi <videolan # tobias-conradi.de>
12 13 14
 *          Ahmad Harb <harb.dev.leb # gmail.com>
 *          Fabio Ritrovato <sephiroth87 # videolan.org>
 *          Pierre SAGASPE <pierre.sagaspe # me.com>
15 16 17 18
 *          Filipe Cabecinhas <vlc # filcab dot net>
 *          Marc Etcheverry <marc # taplightsoftware dot com>
 *          Christopher Loessl <cloessl # x-berg dot de>
 *          Sylver Bruneau <sylver.bruneau # gmail dot com>
19 20 21
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
22

23
#import "VLCMovieViewController.h"
24
#import "VLCExternalDisplayController.h"
25
#import "VLCEqualizerView.h"
26
#import "VLCMultiSelectionMenuView.h"
27 28
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
29
#import "VLCTimeNavigationTitleView.h"
30 31
#import "VLCPlayerDisplayController.h"
#import "VLCAppDelegate.h"
32
#import "VLCStatusLabel.h"
33
#import "VLCMovieViewControlPanelView.h"
34
#import "VLCSlider.h"
35
#import "VLCLibraryViewController.h"
36
#import "VLCTrackSelectorView.h"
37
#import "VLCMetadata.h"
38
#import "UIDevice+VLC.h"
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

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

49 50 51 52
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

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

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

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;

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

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

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

102
    VLCTrackSelectorView *_trackSelectorContainer;
103 104

    VLCEqualizerView *_equalizerView;
105
    VLCMultiSelectionMenuView *_multiSelectionView;
106

107 108
    VLCPlaybackController *_vpc;

109 110
    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;
111
    NSTimer *_sleepCountDownTimer;
112

113
    NSInteger _mediaDuration;
114
    NSInteger _numberOfTapSeek;
Soomin Lee's avatar
Soomin Lee committed
115 116 117 118

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

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

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

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

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

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

    [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
237
    [self sleepTimerInitializer];
238 239 240 241 242 243 244 245
    [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];
246

247 248 249 250
}

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

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

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

    _currentPanType = VLCPanTypeNone;
271 272 273 274
    _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
    [_panRecognizer setMinimumNumberOfTouches:1];
    [_panRecognizer setMaximumNumberOfTouches:1];

275 276
    [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];

277 278 279 280
    _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
    _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
281 282 283 284 285 286
    _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;
287

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

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

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

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

    NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[panel]|"
                                                                    options:0
                                                                    metrics:nil
346
                                                                      views:@{@"panel":_controllerPanel}];
347 348
    [self.view addConstraints:hConstraints];
    [self.view addConstraints:vConstraints];
349 350 351 352 353 354 355 356 357 358

    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];
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

}

- (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];

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

389 390
- (void)viewWillAppear:(BOOL)animated
{
391
    [super viewWillAppear:animated];
392

393 394 395
    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
    self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
    [self setupNavigationbar];
396 397 398 399 400 401
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

402 403 404
    _vpc = [VLCPlaybackController sharedInstance];
    _vpc.delegate = self;
    [_vpc recoverPlaybackState];
405

406
    [self screenBrightnessChanged:nil];
407
    [self setControlsHidden:NO animated:animated];
408 409

    [self updateDefaults];
Soomin Lee's avatar
Soomin Lee committed
410 411 412

    //Disabling video gestures, media not init in the player yet.
    [self enableNormalVideoGestures:NO];
413
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
414 415 416 417 418
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
419
    _viewAppeared = YES;
420
    _playbackWillClose = NO;
421

422 423 424
    [_vpc recoverDisplayedMetadata];
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
425
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
Soomin Lee's avatar
Soomin Lee committed
426 427 428 429 430 431 432 433 434 435

    //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];
436

437 438
}

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

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

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

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

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

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

484
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
485
    [super viewWillDisappear:animated];
486

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

491 492 493
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

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

497 498 499 500 501
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

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

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

511 512
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
513
    [self setControlsHidden:YES animated:flag];
514 515 516
    [super dismissViewControllerAnimated:flag completion:completion];
}

517
- (void)updateDefaults
518 519
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
520 521 522 523 524

    if (!_playbackWillClose) {
        _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
        [self updateTimeDisplayButton];
    }
525 526 527 528 529 530 531 532 533

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

534 535
#pragma mark - Initializer helper

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

545 546 547 548 549 550 551 552 553
    //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;

554
    _sleepTimeDatePicker = [[UIDatePicker alloc] init];
555 556
    _sleepTimeDatePicker.opaque = NO;
    _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
557 558 559 560 561
    _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
    _sleepTimeDatePicker.frame = CGRectMake(0., 0., 300., 162.);
    _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
    [_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];
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607

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

608 609
#pragma mark - controls visibility

610 611
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
612 613
    LOCKCHECK;

Soomin Lee's avatar
Soomin Lee committed
614
    if (!_closeGestureEnabled || isnan(recognizer.velocity))
615 616
        return;

Soomin Lee's avatar
Soomin Lee committed
617 618 619 620 621 622 623 624
    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.) {
625
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
626
    }
627 628
}

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

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
637 638 639 640 641
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

642
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
643
{
644
    _controlsHidden = hidden;
645
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
646

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

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
670
    }
671

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

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
687
    };
688

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

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
703
    };
704

705 706
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
707 708

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
709 710
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
711

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

719 720 721
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

722 723 724
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

725
    [self setControlsHidden:!_controlsHidden animated:YES];
726 727
}

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

742 743 744 745 746 747 748 749 750 751 752 753
- (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];
754

755 756 757
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
758
        [_vpc jumpForward:10];
759 760 761
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
762
        [_vpc jumpBackward:10];
763 764 765 766 767 768 769 770 771
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

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

779 780
    _idleTimer = nil;
    if (!_controlsHidden)
781
        [self toggleControlsVisible];
782

783 784 785 786 787
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

788 789 790
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

791 792 793
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

794 795 796
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
797 798
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
799 800
}

801 802
- (UIResponder *)nextResponder
{
803
    [self _resetIdleTimer];
804 805 806
    return [super nextResponder];
}

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

816 817 818 819
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
820
    LOCKCHECK;
821
    _playbackWillClose = YES;
822
    [_vpc stopPlayback];
823
}
824

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

831 832
- (IBAction)positionSliderAction:(UISlider *)sender
{
833 834
    LOCKCHECK;

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

849 850 851
- (void)_setPositionForReal
{
    if (!_positionSet) {
852
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
853
        [_vpc setNeedsMetadataUpdate];
854 855 856 857
        _positionSet = YES;
    }
}

858 859
- (IBAction)positionSliderTouchDown:(id)sender
{
860 861
    LOCKCHECK;

862 863
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
864
    _isScrubbing = YES;
865 866 867 868
}

- (IBAction)positionSliderTouchUp:(id)sender
{
869 870
    LOCKCHECK;

871
    self.scrubIndicatorView.hidden = YES;
872
    _isScrubbing = NO;
873 874 875 876
}

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

    [self _resetIdleTimer];
888 889 890 891
}

- (IBAction)positionSliderDrag:(id)sender
{
892 893
    LOCKCHECK;

894 895 896
    [self _updateScrubLabel];
}