VLCMovieViewController.m 61.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
    _vpc = [VLCPlaybackController sharedInstance];

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

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

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

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

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

180 181
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
182 183 184 185 186
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification 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
    if ([[UIDevice currentDevice] isiPhoneX]) {
259
        _tapToToggleiPhoneXRatioRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:_vpc action:@selector(switchIPhoneXFullScreen)];
260 261 262 263 264 265
        _tapToToggleiPhoneXRatioRecognizer.numberOfTapsRequired = 2;
        [self.view addGestureRecognizer:_tapToToggleiPhoneXRatioRecognizer];
    } else {
        _tapToSeekRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToSeekRecognized:)];
        [_tapToSeekRecognizer setNumberOfTapsRequired:2];
        [self.view addGestureRecognizer:_tapToSeekRecognizer];
266
        [_tapOnVideoRecognizer requireGestureRecognizerToFail:_tapToSeekRecognizer];
267 268
    }
    _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(togglePlayPause)];
269
    [_tapRecognizer setNumberOfTouchesRequired:2];
270 271

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

    //Disabling video gestures, media not init in the player yet.
    [self enableNormalVideoGestures:NO];
420
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults) name:NSUserDefaultsDidChangeNotification object:nil];
421 422 423 424 425
}

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

429
    [_vpc recoverDisplayedMetadata];
430
    [self resetVideoFiltersSliders];
431 432
    _vpc.videoOutputView = self.movieView;
    _multiSelectionView.repeatMode = _vpc.repeatMode;
Soomin Lee's avatar
Soomin Lee committed
433
    _multiSelectionView.shuffleMode = _vpc.isShuffleMode;
Soomin Lee's avatar
Soomin Lee committed
434 435 436 437 438 439 440 441 442 443

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

446
- (void)viewDidLayoutSubviews
447
{
448 449 450 451 452
    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;

453
    CGRect multiSelectionFrame;
454
    CGRect controllerPanelFrame = _controllerPanel.frame;;
455 456 457 458 459 460 461 462 463 464

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

465
    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
466 467 468 469 470 471 472 473 474
        _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;
475
    }
476
    _multiSelectionView.frame = multiSelectionFrame;
477 478
}

479
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
480
{
481 482
    if (_vpc.videoOutputView == self.movieView) {
        _vpc.videoOutputView = nil;
483
    }
484

485
    _viewAppeared = NO;
486
    if (_idleTimer) {
487
        [_idleTimer invalidate];
488 489
        _idleTimer = nil;
    }
490

491
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
492
    [super viewWillDisappear:animated];
493

Felix Paul Kühne's avatar
Felix Paul Kühne committed
494 495 496 497
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

498 499 500
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
501 502
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
503

504 505 506 507 508
    // reset tap to seek values
    _isTapSeeking = NO;
    _previousJumpState = VLCMovieJumpStateDefault;
    _numberOfTapSeek = 0;

509
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
510
    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
511 512 513 514 515 516 517
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

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

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

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

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

541 542
#pragma mark - Initializer helper

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

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

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

    [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
    [_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];
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614

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

615 616
#pragma mark - controls visibility

617 618
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
619 620
    LOCKCHECK;

Soomin Lee's avatar
Soomin Lee committed
621
    if (!_closeGestureEnabled || isnan(recognizer.velocity))
622 623
        return;

Soomin Lee's avatar
Soomin Lee committed
624 625 626 627 628 629 630 631
    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.) {
632
        [self minimizePlayback:nil];
Soomin Lee's avatar
Soomin Lee committed
633
    }
634 635
}

636 637
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
638
    if (touch.view != self.view)
639 640 641 642 643
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
644 645 646 647 648
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

649
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
650
{
651
    _controlsHidden = hidden;
652
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
653

654
    if (!_controlsHidden) {
655 656
        self.navigationController.navigationBar.alpha = 0.0;
        self.navigationController.navigationBar.hidden = NO;
657
        _controllerPanel.alpha = 0.0f;
658
        _controllerPanel.hidden = !_videoFiltersHidden;
659 660
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
661 662
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
663 664
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
665 666
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
667 668 669 670
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
671 672
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
673 674 675 676

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
677
    }
678

679
    void (^animationBlock)() = ^() {
680
        self.navigationController.navigationBar.alpha = alpha;
681
        _controllerPanel.alpha = alpha;
682
        _videoFilterView.alpha = alpha;
683
        _playbackSpeedView.alpha = alpha;
684
        _trackSelectorContainer.alpha = alpha;
685
        _equalizerView.alpha = alpha;
686
        _multiSelectionView.alpha = alpha;
687 688
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
689 690 691 692 693

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
694
    };
695

696
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
697
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
698
        _videoFilterView.hidden = _videoFiltersHidden;
699
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
700
        self.navigationController.navigationBar.hidden = _controlsHidden;
701
        _trackSelectorContainer.hidden = YES;
702
        _equalizerView.hidden = YES;
703 704
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
705
        _multiSelectionView.hidden = YES;
706 707 708 709

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
710
    };
711

712 713
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
714 715

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
716 717
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
}
718

719
- (void)toggleControlsVisible
720
{
721 722 723 724 725
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

726 727 728
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

729 730 731
    if (_isTapSeeking)
        _numberOfTapSeek = 0;

732
    [self setControlsHidden:!_controlsHidden animated:YES];
733 734
}

735
- (void)_resetIdleTimer
736 737
{
    if (!_idleTimer)
738
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
739 740 741 742 743
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
744 745
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
746 747 748
    }
}

749 750 751 752 753 754 755 756 757 758 759 760
- (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];
761

762 763 764
    int seekDuration = (int)_numberOfTapSeek * SHORT_JUMP_DURATION;

    if (seekDuration > 0) {
765
        [_vpc jumpForward:SHORT_JUMP_DURATION];
766 767 768
        [hudString appendString:@"⇒ "];
        _previousJumpState = VLCMovieJumpStateForward;
    } else {
769
        [_vpc jumpBackward:SHORT_JUMP_DURATION];
770 771 772 773 774 775 776 777 778
        [hudString appendString:@"⇐ "];
        _previousJumpState = VLCMovieJumpStateBackward;
    }
    [hudString appendString: [self _stringInTimeFormatFrom:abs(seekDuration)]];
    [self.statusLabel showStatusMessage:[NSString stringWithString:hudString]];
    if (_controlsHidden)
        [self setControlsHidden:NO animated:NO];
}

779 780
- (void)idleTimerExceeded
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
781 782 783 784 785
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(idleTimerExceeded) withObject:nil waitUntilDone:NO];
        return;
    }

786 787
    _idleTimer = nil;
    if (!_controlsHidden)
788
        [self toggleControlsVisible];
789

790 791 792 793 794
    if (_isTapSeeking) {
        _isTapSeeking = NO;
        _numberOfTapSeek = 0;
    }

795 796 797
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

798 799 800
    if (_equalizerView.hidden == NO)
        _equalizerView.hidden = YES;

801 802 803
    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
804 805
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
806 807
}

808 809
- (UIResponder *)nextResponder
{
810
    [self _resetIdleTimer];
811 812 813
    return [super nextResponder];
}

Soomin Lee's avatar
Soomin Lee committed
814 815 816 817 818 819 820 821 822
- (void)enableNormalVideoGestures:(BOOL)enable
{
    [_tapRecognizer setEnabled:enable];
    [_swipeRecognizerUp setEnabled:enable];
    [_swipeRecognizerDown setEnabled:enable];
    [_swipeRecognizerLeft setEnabled:enable];
    [_swipeRecognizerRight setEnabled:enable];
}

823 824 825 826
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
827
    LOCKCHECK;
828
    _playbackWillClose = YES;
829
    [_vpc stopPlayback];
830
}
831

832 833 834 835
- (IBAction)minimizePlayback:(id)sender
{
    LOCKCHECK;
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
836
}
837

838 839
- (IBAction)positionSliderAction:(UISlider *)sender
{
840 841
    LOCKCHECK;

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

856 857 858
- (void)_setPositionForReal
{
    if (!_positionSet) {
859
        [_vpc setPlaybackPosition:self.timeNavigationTitleView.positionSlider.value];
860
        [_vpc setNeedsMetadataUpdate];
861 862 863 864
        _positionSet = YES;
    }
}

865 866
- (IBAction)positionSliderTouchDown:(id)sender
{
867 868
    LOCKCHECK;

869 870
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
871
    _isScrubbing = YES;
872 873 874 875
}

- (IBAction)positionSliderTouchUp:(id)sender
{
876 877
    LOCKCHECK;

878
    self.scrubIndicatorView.hidden = YES;
879
    _isScrubbing = NO;
880 881 882 883
}

- (void)_updateScrubLabel
{
884
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
885
    if (speed == 1.)
886
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
887
    else if (speed == .5)
888
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
889
    else if (speed == .25)
890
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
891
    else