VLCMovieViewController.m 60.9 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 36
#define FORWARD_SWIPE_DURATION 30
#define BACKWARD_SWIPE_DURATION 10
37

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

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

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

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

    UIActionSheet *_subtitleActionSheet;
    UIActionSheet *_audiotrackActionSheet;

    NSTimer *_idleTimer;
62
    NSTimer *_sleepTimer;
63

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

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

    UIView *_trackSelectorContainer;
    UITableView *_trackSelectorTableView;
87 88

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

    UIView *_sleepTimerContainer;
    UIDatePicker *_sleepTimeDatePicker;

94
    NSInteger _mediaDuration;
95 96
}

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

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

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

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

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

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

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

145 146
    self.wantsFullScreenLayout = YES;

147 148
    self.videoFilterView.hidden = YES;
    _videoFiltersHidden = YES;
149
    _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
150 151
    _hueSlider.accessibilityLabel = _hueLabel.text;
    _hueSlider.isAccessibilityElement = YES;
152
    _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
153 154
    _contrastSlider.accessibilityLabel = _contrastLabel.text;
    _contrastSlider.isAccessibilityElement = YES;
155
    _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
156 157
    _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
    _brightnessSlider.isAccessibilityElement = YES;
158
    _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
159 160
    _saturationSlider.accessibilityLabel = _saturationLabel.text;
    _saturationSlider.isAccessibilityElement = YES;
161
    _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
162 163
    _gammaSlider.accessibilityLabel = _gammaLabel.text;
    _gammaSlider.isAccessibilityElement = YES;
164
    _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
165 166
    _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
    _playbackSpeedSlider.isAccessibilityElement = YES;
167 168 169 170 171 172
    _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;
173

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

212 213 214 215 216 217
    _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
    _multiSelectionView.delegate = self;
    _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _multiSelectionView.hidden = YES;
    [self.view addSubview:_multiSelectionView];

218
    _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
219

220 221
    self.playbackSpeedView.hidden = YES;
    _playbackSpeedViewHidden = YES;
222

223 224 225 226 227
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
                   name:UIScreenDidConnectNotification object:nil];
    [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
                   name:UIScreenDidDisconnectNotification object:nil];
228 229
    [center addObserver:self selector:@selector(screenBrightnessChanged:)
                   name:UIScreenBrightnessDidChangeNotification object:nil];
230

231 232
    _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
    _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
233
    if ([[UIDevice currentDevice] hasExternalDisplay])
234
        [self showOnExternalDisplay];
235

236 237
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";

238
    _movieView.userInteractionEnabled = NO;
239 240 241
    _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
    _tapOnVideoRecognizer.delegate = self;
    [self.view addGestureRecognizer:_tapOnVideoRecognizer];
242

243 244 245
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
    _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
246
    _variableJumpDurationEnabled = [[defaults objectForKey:kVLCSettingVariableJumpDuration] boolValue];
247

248 249 250 251 252 253
    _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];
254 255

    _currentPanType = VLCPanTypeNone;
256 257 258 259 260 261 262 263
    _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;
264 265 266 267 268 269
    _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;
270 271 272

    [self.view addGestureRecognizer:_swipeRecognizerLeft];
    [self.view addGestureRecognizer:_swipeRecognizerRight];
273 274
    [self.view addGestureRecognizer:_swipeRecognizerUp];
    [self.view addGestureRecognizer:_swipeRecognizerDown];
275 276 277 278
    [self.view addGestureRecognizer:_panRecognizer];
    [self.view addGestureRecognizer:_tapRecognizer];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
279 280
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerUp];
    [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerDown];
281 282 283 284

    _panRecognizer.delegate = self;
    _swipeRecognizerRight.delegate = self;
    _swipeRecognizerLeft.delegate = self;
285 286
    _swipeRecognizerUp.delegate = self;
    _swipeRecognizerDown.delegate = self;
287
    _tapRecognizer.delegate = self;
288

Felix Paul Kühne's avatar
Felix Paul Kühne committed
289
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
290
        self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
291
        self.toolbar.tintColor = [UIColor whiteColor];
292
        self.toolbar.barStyle = UIBarStyleBlack;
293

294
        rect = self.resetVideoFilterButton.frame;
295 296
        rect.origin.y = rect.origin.y + 5.;
        self.resetVideoFilterButton.frame = rect;
297 298 299 300
        rect = self.toolbar.frame;
        rect.size.height = rect.size.height + rect.origin.y;
        rect.origin.y = 0;
        self.toolbar.frame = rect;
301 302
    } else {
        [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
303 304 305
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    }
306

307
    /* FIXME: there is a saner iOS 6+ API for this! */
308
    /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
309 310 311 312 313 314 315
    void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
        UISlider *volumeSlider = nil;
        for (id aView in volumeView.subviews){
            if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
                volumeSlider = (UISlider *)aView;
                break;
            }
316
        }
317 318 319 320 321 322 323 324 325 326 327 328 329
        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);
330

331
    _playerIsSetup = NO;
332

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

    rect = self.view.frame;
337 338 339 340 341 342 343 344 345
    CGFloat width;
    CGFloat height;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        width = 300.;
        height = 320.;
    } else {
        width = 450.;
        height = 470.;
    }
346

347
    _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
348 349
    _trackSelectorTableView.delegate = self;
    _trackSelectorTableView.dataSource = self;
350
    _trackSelectorTableView.separatorColor = [UIColor clearColor];
351
    _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
352 353 354
    _trackSelectorTableView.rowHeight = 44.;
    _trackSelectorTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    _trackSelectorTableView.sectionHeaderHeight = 28.;
355 356
    [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
    [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
357
    _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
358

359
    _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
360
    [_trackSelectorContainer addSubview:_trackSelectorTableView];
361
    _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
362 363 364 365 366 367
    _trackSelectorContainer.hidden = YES;

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

    [self.view addSubview:_trackSelectorContainer];
372

373 374 375 376 377
    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.)];
    }
378
    _equalizerView.delegate = self.playbackController;
379
    _equalizerView.UIdelegate = self;
380 381 382
    _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
    _equalizerView.hidden = YES;
    [self.view addSubview:_equalizerView];
383

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
    /* 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]];
            }
416
        }
417 418 419 420 421
        _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
        _sleepTimeDatePicker.minuteInterval = 5;
        _sleepTimeDatePicker.minimumDate = [NSDate date];
        _sleepTimeDatePicker.countDownDuration = 1200.;
        [_sleepTimeDatePicker addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventValueChanged];
422

423 424
        [self.view addSubview:_sleepTimerContainer];
    }
425 426

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidStop:) name:VLCPlaybackControllerPlaybackDidStop object:nil];
427
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
428

429 430
- (void)viewWillAppear:(BOOL)animated
{
431
    [super viewWillAppear:animated];
432

433 434 435 436 437 438
    /* reset audio meta data views */
    self.artworkImageView.image = nil;
    self.trackNameLabel.text = nil;
    self.artistNameLabel.text = nil;
    self.albumNameLabel.text = nil;

439
    _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];
440
    _variableJumpDurationEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingVariableJumpDuration] boolValue];
441

442
    [self.navigationController setNavigationBarHidden:YES animated:YES];
443

Felix Paul Kühne's avatar
Felix Paul Kühne committed
444
    if (!SYSTEM_RUNS_IOS7_OR_LATER) {
445 446 447
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
            [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
    }
448

449
    VLCPlaybackController *vpc = self.playbackController;
450
    vpc.delegate = self;
451
    [vpc recoverPlaybackState];
452 453

    [self setControlsHidden:NO animated:YES];
454 455 456 457 458
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
459
    _viewAppeared = YES;
460

461
    VLCPlaybackController *vpc = self.playbackController;
462
    [vpc recoverDisplayedMetadata];
463 464
    vpc.videoOutputView = nil;
    vpc.videoOutputView = self.movieView;
465 466
}

467 468
- (void)viewWillLayoutSubviews
{
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
    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;
502
    }
503 504

    _multiSelectionView.frame = multiSelectionFrame;
505 506
}

507
- (void)viewWillDisappear:(BOOL)animated
Felix Paul Kühne's avatar
Felix Paul Kühne committed
508
{
509
    VLCPlaybackController *vpc = self.playbackController;
510 511 512
    if (vpc.videoOutputView == self.movieView) {
        vpc.videoOutputView = nil;
    }
513

514
    _viewAppeared = NO;
515
    if (_idleTimer) {
516
        [_idleTimer invalidate];
517 518
        _idleTimer = nil;
    }
519
    [self.navigationController setNavigationBarHidden:NO animated:YES];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
520
    if (!SYSTEM_RUNS_IOS7_OR_LATER)
521
        [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
522
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
523
    [super viewWillDisappear:animated];
524

Felix Paul Kühne's avatar
Felix Paul Kühne committed
525 526 527 528 529 530
    // hide filter UI for next run
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;
531 532

    [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
533 534 535 536 537 538 539
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

540 541
#pragma mark - controls visibility

542 543
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
{
544 545
    LOCKCHECK;

546 547 548
    if (!_swipeGesturesEnabled)
        return;

549 550 551 552
    if (recognizer.velocity < 0.)
        [self closePlayback:nil];
}

553 554
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
555
    if (touch.view != self.view)
556 557 558 559 560
        return NO;

    return YES;
}

Ahmad Harb's avatar
Ahmad Harb committed
561 562 563 564 565
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

566
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
567
{
568
    _controlsHidden = hidden;
569
    CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
570

571 572
    if (!_controlsHidden) {
        _controllerPanel.alpha = 0.0f;
573
        _controllerPanel.hidden = !_videoFiltersHidden;
574 575
        _controllerPanelLandscape.alpha = 0.0f;
        _controllerPanelLandscape.hidden = !_videoFiltersHidden;
576 577
        _toolbar.alpha = 0.0f;
        _toolbar.hidden = NO;
578 579
        _videoFilterView.alpha = 0.0f;
        _videoFilterView.hidden = _videoFiltersHidden;
580 581
        _playbackSpeedView.alpha = 0.0f;
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
582 583
        _trackSelectorContainer.alpha = 0.0f;
        _trackSelectorContainer.hidden = YES;
584 585
        _equalizerView.alpha = 0.0f;
        _equalizerView.hidden = YES;
586 587 588 589
        if (_sleepTimerContainer) {
            _sleepTimerContainer.alpha = 0.0f;
            _sleepTimerContainer.hidden = YES;
        }
590 591
        _multiSelectionView.alpha = 0.0f;
        _multiSelectionView.hidden = YES;
592 593 594 595

        _artistNameLabel.hidden = NO;
        _albumNameLabel.hidden = NO;
        _trackNameLabel.hidden = NO;
596
    }
597

598 599
    void (^animationBlock)() = ^() {
        _controllerPanel.alpha = alpha;
600
        _controllerPanelLandscape.alpha = alpha;
601
        _toolbar.alpha = alpha;
602
        _videoFilterView.alpha = alpha;
603
        _playbackSpeedView.alpha = alpha;
604
        _trackSelectorContainer.alpha = alpha;
605
        _equalizerView.alpha = alpha;
606
        _multiSelectionView.alpha = alpha;
607 608
        if (_sleepTimerContainer)
            _sleepTimerContainer.alpha = alpha;
609 610 611 612 613

        CGFloat metaInfoAlpha = _audioOnly ? 1.0f : alpha;
        _artistNameLabel.alpha = metaInfoAlpha;
        _albumNameLabel.alpha = metaInfoAlpha;
        _trackNameLabel.alpha = metaInfoAlpha;
614
    };
615

616
    void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
617 618
        _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
        _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
619
        _toolbar.hidden = _controlsHidden;
620
        _videoFilterView.hidden = _videoFiltersHidden;
621
        _playbackSpeedView.hidden = _playbackSpeedViewHidden;
622
        _trackSelectorContainer.hidden = YES;
623
        _equalizerView.hidden = YES;
624 625
        if (_sleepTimerContainer)
            _sleepTimerContainer.hidden = YES;
626
        _multiSelectionView.hidden = YES;
627 628 629 630

        _artistNameLabel.hidden = _audioOnly ? NO : _controlsHidden;
        _albumNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
        _trackNameLabel.hidden =  _audioOnly ? NO : _controlsHidden;
631
    };
632

633 634
    UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
    NSTimeInterval animationDuration = animated? 0.3: 0.0;
635 636

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

639
    _volumeView.hidden = _volumeViewLandscape.hidden = _controllerPanel.hidden;
640
}
641

642
- (void)toggleControlsVisible
643
{
644 645 646 647 648
    if (!_trackSelectorContainer.hidden) {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
            [recognizer setEnabled:YES];
    }

649 650 651
    if (_controlsHidden && !_videoFiltersHidden)
        _videoFiltersHidden = YES;

652
    [self setControlsHidden:!_controlsHidden animated:YES];
653 654
}

655
- (void)_resetIdleTimer
656 657
{
    if (!_idleTimer)
658
        _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
659 660 661 662 663
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO];
    else {
664 665
        if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
            [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
666 667 668 669 670 671 672
    }
}

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

675 676 677 678 679 680
    if (!_videoFiltersHidden)
        _videoFiltersHidden = YES;

    if (!_playbackSpeedViewHidden)
        _playbackSpeedViewHidden = YES;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
681 682
    if (self.scrubIndicatorView.hidden == NO)
        self.scrubIndicatorView.hidden = YES;
683 684
}

685 686
- (UIResponder *)nextResponder
{
687
    [self _resetIdleTimer];
688 689 690
    return [super nextResponder];
}

691 692 693 694
#pragma mark - controls

- (IBAction)closePlayback:(id)sender
{
695
    LOCKCHECK;
696
    [[UIApplication sharedApplication] sendAction:@selector(closeFullscreenPlayback) to:nil from:self forEvent:nil];
697

698
}
699 700
- (IBAction)positionSliderAction:(UISlider *)sender
{
701 702
    LOCKCHECK;

703 704 705
    /* 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. */
706
    [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
707
    if (_mediaDuration > 0) {
708 709
        VLCTime *newPosition = [VLCTime timeWithInt:(int)(sender.value * _mediaDuration)];
        [self.timeNavigationTitleView.timeDisplayButton setTitle:newPosition.stringValue forState:UIControlStateNormal];
710
        [self.timeNavigationTitleView setNeedsLayout];
711
        self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
712 713
        _positionSet = NO;
    }
714
    [self _resetIdleTimer];
715 716
}

717 718 719
- (void)_setPositionForReal
{
    if (!_positionSet) {
720 721
        self.playbackController.mediaPlayer.position = self.timeNavigationTitleView.positionSlider.value;
        [self.playbackController setNeedsMetadataUpdate];
722 723 724 725
        _positionSet = YES;
    }
}

726 727
- (IBAction)positionSliderTouchDown:(id)sender
{
728 729
    LOCKCHECK;

730 731
    [self _updateScrubLabel];
    self.scrubIndicatorView.hidden = NO;
732
    _isScrubbing = YES;
733 734 735 736
}

- (IBAction)positionSliderTouchUp:(id)sender
{
737 738
    LOCKCHECK;

739
    self.scrubIndicatorView.hidden = YES;
740
    _isScrubbing = NO;
741 742 743 744
}

- (void)_updateScrubLabel
{
745
    float speed = self.timeNavigationTitleView.positionSlider.scrubbingSpeed;
746
    if (speed == 1.)
747
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
748
    else if (speed == .5)
749
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
750
    else if (speed == .25)
751
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
752
    else
753
        self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
754 755

    [self _resetIdleTimer];
756 757 758 759
}

- (IBAction)positionSliderDrag:(id)sender
{
760 761
    LOCKCHECK;

762 763 764
    [self _updateScrubLabel];
}

765 766
- (IBAction)volumeSliderAction:(id)sender
{
767 768
    LOCKCHECK;

769 770 771
    [self _resetIdleTimer];
}

772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
- (void)playPauseLongPress:(UILongPressGestureRecognizer *)recognizer
{
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
        {   UIImage *image = [UIImage imageNamed:@"stopIcon"];
            [_playPauseButton setImage:image forState:UIControlStateNormal];
            [_playPauseButtonLandscape setImage:image forState:UIControlStateNormal];
        }
            break;
        case UIGestureRecognizerStateEnded:
            [self.playbackController stopPlayback];
            break;
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
            [self updatePlayPauseButton];
            break;
        default:
            break;
    }
}

- (void)updatePlayPauseButton
{
    const BOOL isPlaying = self.playbackController.isPlaying;
    UIImage *playPauseImage = isPlaying ? [UIImage imageNamed:@"pauseIcon"] : [UIImage imageNamed:@"playIcon"];
    [_playPauseButton setImage:playPauseImage forState:UIControlStateNormal];
    [_playPauseButtonLandscape setImage:playPauseImage forState:UIControlStateNormal];
}

801 802
#pragma mark - playback controller delegation

803 804 805 806 807 808 809 810
- (VLCPlaybackController *)playbackController
{
    if (!_playbackController) {
        _playbackController = [VLCPlaybackController sharedInstance];
    }
    return _playbackController;
}

811
- (void)playbackPositionUpdated:(VLCPlaybackController *)controller
812
{
813
    VLCMediaPlayer *mediaPlayer = controller.mediaPlayer;
814
    if (!_isScrubbing) {
815
        self.timeNavigationTitleView.positionSlider.value = [mediaPlayer position];
816 817
    }

818
    if (_displayRemainingTime)
819
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
820
    else
821
        [self.timeNavigationTitleView.timeDisplayButton setTitle:[[mediaPlayer time] stringValue] forState:UIControlStateNormal];
822
    [self.timeNavigationTitleView setNeedsLayout];
823 824
}

825
- (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
826
{
827
    self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
828 829 830
    self.timeNavigationTitleView.positionSlider.value = 0.;
    [self.timeNavigationTitleView.timeDisplayButton setTitle:@"" forState:UIControlStateNormal];
    self.timeNavigationTitleView.timeDisplayButton.accessibilityLabel = @"";