VLCMovieViewController.m 59.4 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCMovieViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 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 26
#import "VLCTrackSelectorTableViewCell.h"
#import "VLCTrackSelectorHeaderView.h"
27
#import "VLCEqualizerView.h"
28
#import "VLCMultiSelectionMenuView.h"
29 30
#import "VLCPlaybackController.h"
#import "UIDevice+VLC.h"
31
#import "VLCTimeNavigationTitleView.h"
32 33

#import "VLCStatusLabel.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
34

35
#define INPUT_RATE_DEFAULT  1000.
36 37
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
38

39 40 41
#define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
#define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"

42 43 44 45
#define LOCKCHECK \
if (_interfaceIsLocked) \
return

46 47 48 49 50 51 52
typedef NS_ENUM(NSInteger, VLCPanType) {
  VLCPanTypeNone,
  VLCPanTypeBrightness,
  VLCPanTypeSeek,
  VLCPanTypeVolume,
};

53
@interface VLCMovieViewController () <UIGestureRecognizerDelegate, UITableViewDataSource, UITableViewDelegate, VLCMultiSelectionViewDelegate, VLCEqualizerViewUIDelegate>
54
{
55 56 57 58 59 60 61 62
    BOOL _controlsHidden;
    BOOL _videoFiltersHidden;
    BOOL _playbackSpeedViewHidden;

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;
63
    NSTimer *_sleepTimer;
64

65
    BOOL _viewAppeared;
66
    BOOL _displayRemainingTime;
67
    BOOL _positionSet;
68
    BOOL _playerIsSetup;
69
    BOOL _isScrubbing;
70
    BOOL _interfaceIsLocked;
71
    BOOL _switchingTracksNotChapters;
72
    BOOL _audioOnly;
Ahmad Harb's avatar
Ahmad Harb committed
73 74

    BOOL _swipeGesturesEnabled;
75
    BOOL _variableJumpDurationEnabled;
76
    UIPinchGestureRecognizer *_pinchRecognizer;
77
    VLCPanType _currentPanType;
Ahmad Harb's avatar
Ahmad Harb committed
78 79 80
    UIPanGestureRecognizer *_panRecognizer;
    UISwipeGestureRecognizer *_swipeRecognizerLeft;
    UISwipeGestureRecognizer *_swipeRecognizerRight;
81 82
    UISwipeGestureRecognizer *_swipeRecognizerUp;
    UISwipeGestureRecognizer *_swipeRecognizerDown;
83
    UITapGestureRecognizer *_tapRecognizer;
84
    UITapGestureRecognizer *_tapOnVideoRecognizer;
85 86 87

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
88 89

    VLCEqualizerView *_equalizerView;
90
    VLCMultiSelectionMenuView *_multiSelectionView;
91 92 93 94

    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;

95
    NSInteger _mediaDuration;
96 97
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
98 99
@property (nonatomic, strong) UIPopoverController *masterPopoverController;
@property (nonatomic, strong) UIWindow *externalWindow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
100 101
@end

102
@implementation VLCMovieViewController
Felix Paul Kühne's avatar
Felix Paul Kühne committed
103

104 105 106 107 108 109 110
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
111 112
- (void)dealloc
{
113 114
    [[NSNotificationCenter defaultCenter] removeObserver:self];

115
    if (_tapRecognizer)
116
        [self.view removeGestureRecognizer:_tapRecognizer];
Ahmad Harb's avatar
Ahmad Harb committed
117
    if (_swipeRecognizerLeft)
118
        [self.view removeGestureRecognizer:_swipeRecognizerLeft];
Ahmad Harb's avatar
Ahmad Harb committed
119
    if (_swipeRecognizerRight)
120
        [self.view removeGestureRecognizer:_swipeRecognizerRight];
121 122 123 124
    if (_swipeRecognizerUp)
        [self.view removeGestureRecognizer:_swipeRecognizerUp];
    if (_swipeRecognizerDown)
        [self.view removeGestureRecognizer:_swipeRecognizerDown];
Ahmad Harb's avatar
Ahmad Harb committed
125
    if (_panRecognizer)
126
        [self.view removeGestureRecognizer:_panRecognizer];
127 128 129 130
    if (_pinchRecognizer)
        [self.view removeGestureRecognizer:_pinchRecognizer];
    [self.view removeGestureRecognizer:_tapOnVideoRecognizer];

131 132 133
    _tapRecognizer = nil;
    _swipeRecognizerLeft = nil;
    _swipeRecognizerRight = nil;
134 135
    _swipeRecognizerUp = nil;
    _swipeRecognizerDown = nil;
136
    _panRecognizer = nil;
137 138
    _pinchRecognizer = nil;
    _tapOnVideoRecognizer = nil;
139 140
}

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

146 147
    self.wantsFullScreenLayout = YES;

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

175 176 177 178
    _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButton.isAccessibilityElement = YES;
    _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
    _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
179 180
    _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButton.isAccessibilityElement = YES;
181 182
    _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
183
    _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
184
    _videoFilterButton.isAccessibilityElement = YES;
185
    _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
186
    _videoFilterButtonLandscape.isAccessibilityElement = YES;
187
    _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
188
    _resetVideoFilterButton.isAccessibilityElement = YES;
189
    _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
190
    _playPauseButton.isAccessibilityElement = YES;
191
    _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
192
    _playPauseButtonLandscape.isAccessibilityElement = YES;
193
    _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
194
    _bwdButton.isAccessibilityElement = YES;
195
    _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
196
    _bwdButtonLandscape.isAccessibilityElement = YES;
197
    _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
198
    _fwdButton.isAccessibilityElement = YES;
199
    _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
200
    _fwdButtonLandscape.isAccessibilityElement = YES;
201 202 203
    _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
    _sleepTimerButton.isAccessibilityElement = YES;
    [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
204 205
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
        _sleepTimerButton.hidden = YES;
206

207 208 209 210 211 212
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

213
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
214

215 216
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
217

218 219 220 221 222 223
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];

224 225
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
226
    if ([[UIDevice currentDevice] hasExternalDisplay])
227
        [self showOnExternalDisplay];
228

229 230
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

231
    _movieView.userInteractionEnabled = NO;
232 233 234
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
235

236 237 238
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
239
    _variableJumpDurationEnabled = [[defaults objectForKey:kVLCSettingVariableJumpDuration] boolValue];
240

241 242 243 244 245 246
    _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    _pinchRecognizer.delegate = self;
    [self.view addGestureRecognizer:_pinchRecognizer];

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

    _currentPanType = VLCPanTypeNone;
249 250 251 252 253 254 255 256
    _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;
257 258 259 260 261 262
    _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;
263 264 265

    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
266 267
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
268 269 270 271
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
272 273
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
274 275 276 277

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
278 279
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
280
    _tapRecognizer.delegate = self;
281

Felix Paul Kühne's avatar
Felix Paul Kühne committed
282
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
283
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
284
        self.toolbar.tintColor = [UIColor whiteColor];
285
        self.toolbar.barStyle = UIBarStyleBlack;
286

287
        rect = self.resetVideoFilterButton.frame;
288 289
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
290 291 292 293
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
294 295
    } else {
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
296 297 298
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
299

300
    /* FIXME: there is a saner iOS 6+ API for this! */
301
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
302 303 304 305 306 307 308
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
309
        }
310 311 312 313 314 315 316 317 318 319 320 321 322
        if (!SYSTEM_RUNS_IOS7_OR_LATER) {
            [volumeSlider setMinimumTrackImage:[[UIImage imageNamed:@"sliderminiValue"]resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 0)] forState:UIControlStateNormal];
            [volumeSlider setMaximumTrackImage:[[UIImage imageNamed:@"slidermaxValue"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 4)] forState:UIControlStateNormal];
            [volumeSlider setThumbImage:[UIImage imageNamed:@"volumeballslider"] forState:UIControlStateNormal];
        } else
            [volumeView setVolumeThumbImage:[UIImage imageNamed:@"modernSliderKnob"] forState:UIControlStateNormal];
        [volumeSlider addTarget:self
                         action:@selector(volumeSliderAction:)
               forControlEvents:UIControlEventValueChanged];
    };

    initVolumeSlider(self.volumeView);
    initVolumeSlider(self.volumeViewLandscape);
323

324
    _playerIsSetup = NO;
325

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

    rect = self.view.frame;
330 331 332 333 334 335 336 337 338
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
        width = 450.;
        height = 470.;
    }
339

340
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
341 342
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
343
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
344 345 346
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
347
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
348

349
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
350
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
351
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
352 353 354 355 356 357
    _trackSelectorContainer.hidden = YES;

    if ([[UIDevice currentDevice] speedCategory] >= 3) {
        _trackSelectorTableView.opaque = NO;
        _trackSelectorTableView.backgroundColor = [UIColor clearColor];
    } else
358
        _trackSelectorTableView.backgroundColor = [UIColor blackColor];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
359
    _trackSelectorTableView.allowsMultipleSelection = YES;
360 361

    [self.view addSubview:_trackSelectorContainer];
362

363 364 365 366 367
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 200., 450., 240.)];
    } else {
        _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 240., 450., 240.)];
    }
368 369
    _equalizerView.delegate = [VLCPlaybackController sharedInstance];
    _equalizerView.UIdelegate = self;
370 371 372
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
373

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
    /* add sleep timer UI, requires iOS 7 or later */
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
        _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 162.)];
        _sleepTimerContainer.center = self.view.center;
        _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;

        _sleepTimeDatePicker = [[UIDatePicker alloc] init];
        if ([[UIDevice currentDevice] speedCategory] >= 3) {
            _sleepTimeDatePicker.opaque = NO;
            _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
        } else
            _sleepTimeDatePicker.backgroundColor = [UIColor blackColor];
        _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
        _sleepTimeDatePicker.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
        [_sleepTimerContainer addSubview:_sleepTimeDatePicker];

        /* 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]];
            }
406
        }
407 408 409 410 411
        _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
        _sleepTimeDatePicker.minuteInterval = 5;
        _sleepTimeDatePicker.minimumDate = [NSDate date];
        _sleepTimeDatePicker.countDownDuration = 1200.;
        [_sleepTimeDatePicker addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventValueChanged];
412

413 414
        [self.view addSubview:_sleepTimerContainer];
    }
415
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
416

417 418
- (void)viewWillAppear:(BOOL)animated
{
419
    [super viewWillAppear:animated];
420

421 422 423 424 425 426
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

427
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];
428
    _variableJumpDurationEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingVariableJumpDuration] boolValue];
429

430
    [self.navigationController setNavigationBarHidden:YES animated:YES];
431

Felix Paul Kühne's avatar
Felix Paul Kühne committed
432
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
433 434 435
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
436

437 438 439
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    vpc.videoOutputView = self.movieView;
    vpc.delegate = self;
440 441

    [self setControlsHidden:NO animated:YES];
442 443 444 445 446
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
447
    _viewAppeared = YES;
448
    [[VLCPlaybackController sharedInstance] recoverDisplayedMetadata];
449 450
}

451 452
- (void)viewWillLayoutSubviews
{
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
    CGRect multiSelectionFrame;
    CGRect controllerPanelFrame;

    if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone) {
        controllerPanelFrame = _controllerPanel.frame;
        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;
    }

    CGSize viewSize = self.view.frame.size;

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
        [_controllerPanel removeFromSuperview];
        controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
        _controllerPanelLandscape.frame = controllerPanelFrame;
        [self.view addSubview:_controllerPanelLandscape];
        _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 {
        [_controllerPanelLandscape removeFromSuperview];
        controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
        _controllerPanel.frame = controllerPanelFrame;
        [self.view addSubview:_controllerPanel];
        _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 488

    _multiSelectionView.frame = multiSelectionFrame;
489 490
}

491
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
492
{
493 494
    [VLCPlaybackController sharedInstance].videoOutputView = nil;

495
    _viewAppeared = NO;
496
    if (_idleTimer) {
497
        [_idleTimer invalidate];
498 499
        _idleTimer = nil;
    }
500
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
501
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
502
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
503
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
504
    [super viewWillDisappear:animated];
505

Felix Paul Kühne's avatar
Felix Paul Kühne committed
506 507 508 509 510 511
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
512 513

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
514 515 516 517 518 519 520
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

521 522
#pragma mark - controls visibility

523 524
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
525 526
    LOCKCHECK;

527 528 529
    if (!_swipeGesturesEnabled)
        return;

530 531 532 533
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

534 535
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
536
    if (touch.view != self.view)
537 538 539 540 541
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
542 543 544 545 546
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

547
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
548
{
549
    _controlsHidden = hidden;
550
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
551

552 553
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
554
        _controllerPanel.hidden = !_videoFiltersHidden;
555 556
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
557 558
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
559 560
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
561 562
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
563 564
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
565 566
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
567 568 569 570
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
571 572
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
573 574 575 576

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
577
    }
578

579 580
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
581
        _controllerPanelLandscape.alpha = alpha;
582
        _toolbar.alpha = alpha;
583
        _videoFilterView.alpha = alpha;
584
        _playbackSpeedView.alpha = alpha;
585
        _trackSelectorContainer.alpha = alpha;
586
        _equalizerView.alpha = alpha;
587
        _multiSelectionView.alpha = alpha;
588 589
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
590 591 592 593 594

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
595
    };
596

597
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
598 599
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
        _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
600
        _toolbar.hidden = _controlsHidden;
601
        _videoFilterView.hidden = _videoFiltersHidden;
602
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
603
        _trackSelectorContainer.hidden = YES;
604
        _equalizerView.hidden = YES;
605 606
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
607
        _multiSelectionView.hidden = YES;
608 609 610 611

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
612
    };
613

614 615
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
616 617

    [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
618
    [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
619

620
    _volumeView.hidden = _volumeViewLandscape.hidden = _controllerPanel.hidden;
621
}
622

623
- (void)toggleControlsVisible
624
{
625 626 627 628 629
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

630 631 632
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

633
    [self setControlsHidden:!_controlsHidden animated:YES];
634 635
}

636
- (void)_resetIdleTimer
637 638
{
    if (!_idleTimer)
639
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
640 641 642 643 644
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
645 646
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
647 648 649 650 651 652 653
    }
}

- (void)idleTimerExceeded
{
    _idleTimer = nil;
    if (!_controlsHidden)
654
        [self toggleControlsVisible];
655

656 657 658 659 660 661
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
662 663
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
664 665
}

666 667
- (UIResponder *)nextResponder
{
668
    [self _resetIdleTimer];
669 670 671
    return [super nextResponder];
}

672 673 674 675
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
676 677
    LOCKCHECK;

678
    [self presentingViewControllerShouldBeClosed:nil];
679 680
}

681 682
- (IBAction)positionSliderAction:(UISlider *)sender
{
683 684
    LOCKCHECK;

685 686 687
    /* 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. */
688
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
689
    if (_mediaDuration > 0) {
690 691 692
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
693 694
        _positionSet = NO;
    }
695
    [self _resetIdleTimer];
696 697
}

698 699 700
- (void)_setPositionForReal
{
    if (!_positionSet) {
701
        [VLCPlaybackController sharedInstance].mediaPlayer.position = self.timeNavigationTitleView.positionSlider.value;
702
        [[VLCPlaybackController sharedInstance] setNeedsMetadataUpdate];
703 704 705 706
        _positionSet = YES;
    }
}

707 708
- (IBAction)positionSliderTouchDown:(id)sender
{
709 710
    LOCKCHECK;

711 712
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
713
    _isScrubbing = YES;
714 715 716 717
}

- (IBAction)positionSliderTouchUp:(id)sender
{
718 719
    LOCKCHECK;

720
    self.scrubIndicatorView.hidden = YES;
721
    _isScrubbing = NO;
722 723 724 725
}

- (void)_updateScrubLabel
{
726
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
727
    if (speed == 1.)
728
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
729
    else if (speed == .5)
730
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
731
    else if (speed == .25)
732
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
733
    else
734
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
735 736

    [self _resetIdleTimer];
737 738 739 740
}

- (IBAction)positionSliderDrag:(id)sender
{
741 742
    LOCKCHECK;

743 744 745
    [self _updateScrubLabel];
}

746 747
- (IBAction)volumeSliderAction:(id)sender
{
748 749
    LOCKCHECK;

750 751 752
    [self _resetIdleTimer];
}

753 754 755
#pragma mark - playback controller delegation

- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
756
{
757
    VLCMediaPlayer *mediaPlayer = controller.mediaPlayer;
758
    if (!_isScrubbing) {
759
        self.timeNavigationTitleView.positionSlider.value = [mediaPlayer position];
760 761
    }

762
    if (_displayRemainingTime)
763
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
764
    else
765
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer time] stringValue] forState:UIControlStateNormal];
766 767
}

768
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
769
{
770
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
771 772 773
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";
774 775 776 777 778 779 780 781
    if (![[UIDevice currentDevice] hasExternalDisplay])
        self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
    [_equalizerView reloadData];

    float playbackRate = controller.playbackRate;
    self.playbackSpeedSlider.value = playbackRate;
    double speed = pow(2, playbackRate / 17.);
    self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", speed];
782

783
    VLCMediaPlayer *mediaPlayer = [VLCPlaybackController sharedInstance].mediaPlayer;
784

785 786
    self.audioDelaySlider.value = mediaPlayer.currentAudioPlaybackDelay / 1000000;
    self.audioDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.audioDelaySlider.value];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
787

788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
    self.spuDelaySlider.value = mediaPlayer.currentVideoSubTitleDelay / 1000000;
    self.spuDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.spuDelaySlider.value];

    [self _resetIdleTimer];
}

- (void)presentingViewControllerShouldBeClosed:(VLCPlaybackController *)controller
{
    [self setControlsHidden:NO animated:NO];
    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
}

- (void)presentingViewControllerShouldBeClosedAfterADelay:(VLCPlaybackController *)controller
{
    [self performSelector:@selector(presentingViewControllerShouldBeClosed:) withObject:nil afterDelay:2.];
}

- (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
                      isPlaying:(BOOL)isPlaying
currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
        currentMediaHasChapters:(BOOL)currentMediaHasChapters
          forPlaybackController:(VLCPlaybackController *)controller
{
    if (currentState == VLCMediaPlayerStateBuffering)
        _mediaDuration = controller.mediaDuration;

    if (currentState == VLCMediaPlayerStateError)
815
        [self.statusLabel showStatusMessage:NSLocalizedString(@"PLAYBACK_FAILED", nil)];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
816

817
    UIImage *playPauseImage = isPlaying ? [UIImage imageNamed:@"pauseIcon"] : [UIImage imageNamed:@"playIcon"];
Gleb Pinigin's avatar
Gleb Pinigin committed
818
    [_playPauseButton setImage:playPauseImage forState:UIControlStateNormal];
819
    [_playPauseButtonLandscape setImage:playPauseImage forState:UIControlStateNormal];
820

821
    if (currentMediaHasTrackToChooseFrom) {
822 823
        self.trackSwitcherButton.hidden = NO;
        self.trackSwitcherButtonLandscape.hidden = NO;
824
    } else {
825 826
        self.trackSwitcherButton.hidden = YES;
        self.trackSwitcherButtonLandscape.hidden = YES;
827
    }
828

829
    if (currentMediaHasChapters)
830 831 832
        _multiSelectionView.mediaHasChapters = YES;
    else
        _multiSelectionView.mediaHasChapters = NO;