/* HBPlayerHUDController.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBPlayerHUDController.h"
@interface HBPlayerHUDController ()
@property (weak) IBOutlet NSButton *playButton;
@property (weak) IBOutlet NSSlider *slider;
@property (weak) IBOutlet NSSlider *volumeSlider;
@property (weak) IBOutlet NSTextField *currentTimeLabel;
@property (weak) IBOutlet NSTextField *remaingTimeLabel;
@property (weak) IBOutlet NSPopUpButton *tracksSelection;
@property (nonatomic, readonly) NSDictionary *monospacedAttr;
@property (nonatomic, readonly) NSDictionary *normalMonospacedAttr;
@property (nonatomic, readwrite) id rateObserver;
@property (nonatomic, readwrite) id periodicObserver;
@end
@interface HBPlayerHUDController (TouchBar)
- (void)_touchBar_updatePlayState:(BOOL)playing;
- (void)_touchBar_updateMaxDuration:(NSTimeInterval)duration;
- (void)_touchBar_updateTime:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration;
@end
@implementation HBPlayerHUDController
- (NSString *)nibName
{
return @"HBPlayerHUDController";
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[NSFont class] respondsToSelector:@selector(monospacedDigitSystemFontOfSize:weight:)]) {
_normalMonospacedAttr = @{NSFontAttributeName: [NSFont monospacedDigitSystemFontOfSize:15 weight:NSFontWeightRegular]};
_monospacedAttr = @{NSFontAttributeName: [NSFont monospacedDigitSystemFontOfSize:[NSFont smallSystemFontSize] weight:NSFontWeightRegular]};
}
else {
_monospacedAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]};
}
}
- (BOOL)canBeHidden
{
return YES;
}
- (void)setPlayer:(id)player
{
if (_player)
{
[self.player removeRateObserver:self.rateObserver];
[self.player removeTimeObserver:self.periodicObserver];
self.rateObserver = nil;
self.periodicObserver = nil;
[self _clearTracksMenu];
}
_player = player;
if (player)
{
[self _buildTracksMenu];
__weak HBPlayerHUDController *weakSelf = self;
self.periodicObserver = [self.player addPeriodicTimeObserverUsingBlock:^(NSTimeInterval time) {
[weakSelf _refreshUI];
}];
self.rateObserver = [self.player addRateObserverUsingBlock:^{
[weakSelf _refreshPlayButtonState];
}];
NSTimeInterval duration = self.player.duration;
[self.slider setMinValue:0.0];
[self.slider setMaxValue:duration];
[self.slider setDoubleValue:0.0];
if (@available(macOS 10.12.2, *))
{
[self _touchBar_updateMaxDuration:duration];
}
self.player.volume = self.volumeSlider.floatValue;
[self.player play];
}
}
- (void)dealloc
{
if (_rateObserver)
{
[_player removeRateObserver:_rateObserver];
_rateObserver = nil;
}
if (_periodicObserver)
{
[_player removeTimeObserver:_periodicObserver];
_periodicObserver = nil;
}
}
#pragma mark - Audio and subtitles selection menu
- (void)_buildTracksMenu
{
[self _clearTracksMenu];
NSArray *audioTracks = self.player.audioTracks;
if (audioTracks.count)
{
[self _addSectionTitle:NSLocalizedString(@"Audio", @"Player HUD -> audio menu")];
[self _addTracksItemFromArray:audioTracks selector:@selector(enableAudioTrack:)];
}
NSArray *subtitlesTracks = self.player.subtitlesTracks;
if (subtitlesTracks.count)
{
if (audioTracks.count)
{
[self.tracksSelection.menu addItem:[NSMenuItem separatorItem]];
}
[self _addSectionTitle:NSLocalizedString(@"Subtitles", @"Player HUD -> subtitles menu")];
[self _addTracksItemFromArray:subtitlesTracks selector:@selector(enableSubtitlesTrack:)];
}
}
- (void)_clearTracksMenu
{
for (NSMenuItem *item in [self.tracksSelection.menu.itemArray copy])
{
if (item.tag != 1)
{
[self.tracksSelection.menu removeItem:item];
}
}
}
- (void)_addSectionTitle:(NSString *)title
{
NSMenuItem *sectionTitle = [[NSMenuItem alloc] init];
sectionTitle.title = title;
sectionTitle.enabled = NO;
sectionTitle.indentationLevel = 0;
[self.tracksSelection.menu addItem:sectionTitle];
}
- (void)_addTracksItemFromArray:(NSArray *)tracks selector:(SEL)selector
{
for (HBPlayerTrack *track in tracks)
{
NSMenuItem *item = [[NSMenuItem alloc] init];
item.title = track.name;
item.enabled = YES;
item.indentationLevel = 1;
item.action = selector;
item.target = self;
item.state = track.enabled;
item.representedObject = track;
[self.tracksSelection.menu addItem:item];
}
}
- (void)_updateTracksMenuState
{
for (NSMenuItem *item in self.tracksSelection.menu.itemArray)
{
if (item.representedObject)
{
HBPlayerTrack *track = (HBPlayerTrack *)item.representedObject;
item.state = track.enabled;
}
}
}
- (IBAction)enableAudioTrack:(NSMenuItem *)sender
{
[self.player enableAudioTrack:sender.representedObject];
[self _updateTracksMenuState];
}
- (IBAction)enableSubtitlesTrack:(NSMenuItem *)sender
{
[self.player enableSubtitlesTrack:sender.representedObject];
[self _updateTracksMenuState];
}
- (NSString *)_timeToTimecode:(NSTimeInterval)timeInSeconds
{
UInt16 seconds = (UInt16)fmod(timeInSeconds, 60.0);
UInt16 minutes = (UInt16)fmod(timeInSeconds / 60.0, 60.0);
UInt16 milliseconds = (UInt16)((timeInSeconds - (int) timeInSeconds) * 1000);
return [NSString stringWithFormat:@"%02d:%02d.%03d", minutes, seconds, milliseconds];
}
- (NSAttributedString *)_smallMonospacedString:(NSString *)string
{
return [[NSAttributedString alloc] initWithString:string attributes:self.monospacedAttr];
}
- (void)_refreshUI
{
if (self.player)
{
NSTimeInterval currentTime = self.player.currentTime;
NSTimeInterval duration = self.player.duration;
self.slider.doubleValue = currentTime;
self.currentTimeLabel.attributedStringValue = [self _smallMonospacedString:[self _timeToTimecode:currentTime]];
self.remaingTimeLabel.attributedStringValue = [self _smallMonospacedString:[self _timeToTimecode:duration - currentTime]];
if (@available(macOS 10.12.2, *))
{
[self _touchBar_updateTime:currentTime duration:duration];
}
}
}
- (void)_refreshPlayButtonState
{
BOOL playing = self.player.rate != 0.0;
if (playing)
{
self.playButton.image = [NSImage imageNamed:@"PauseTemplate"];
}
else
{
self.playButton.image = [NSImage imageNamed:@"PlayTemplate"];
}
if (@available(macOS 10.12.2, *))
{
[self _touchBar_updatePlayState:playing];
}
}
- (IBAction)playPauseToggle:(id)sender
{
if (self.player.rate != 0.0)
{
[self.player pause];
}
else
{
[self.player play];
}
}
- (IBAction)goToBeginning:(id)sender
{
[self.player gotoBeginning];
}
- (IBAction)goToEnd:(id)sender
{
[self.player gotoEnd];
}
- (IBAction)showPicturesPreview:(id)sender
{
[self.delegate stopPlayer];
}
- (IBAction)sliderChanged:(NSSlider *)sender
{
self.player.currentTime = sender.doubleValue;
}
- (IBAction)maxVolume:(id)sender
{
self.volumeSlider.doubleValue = 1;
self.player.volume = 1;
}
- (IBAction)mute:(id)sender
{
self.volumeSlider.doubleValue = 0;
self.player.volume = 0;
}
- (IBAction)volumeSliderChanged:(NSSlider *)sender
{
self.player.volume = sender.floatValue;
}
#pragma mark - Keyboard and mouse wheel control
- (BOOL)HB_keyDown:(NSEvent *)event
{
unichar key = [event.charactersIgnoringModifiers characterAtIndex:0];
if (self.player)
{
if (key == 32)
{
if (self.player.rate != 0.0)
[self.player pause];
else
[self.player play];
}
else if (key == 'k')
[self.player pause];
else if (key == 'l')
{
float rate = self.player.rate;
rate += 1.0f;
[self.player play];
self.player.rate = rate;
}
else if (key == 'j')
{
float rate = self.player.rate;
rate -= 1.0f;
[self.player play];
self.player.rate = rate;
}
else if (event.modifierFlags & NSEventModifierFlagOption && key == NSLeftArrowFunctionKey)
{
[self.player gotoBeginning];
}
else if (event.modifierFlags & NSEventModifierFlagOption && key == NSRightArrowFunctionKey)
{
[self.player gotoEnd];
}
else if (key == NSLeftArrowFunctionKey)
{
[self.player stepBackward];
}
else if (key == NSRightArrowFunctionKey)
{
[self.player stepForward];
}
else
{
return NO;
}
}
else
{
return NO;
}
return YES;
}
- (BOOL)HB_scrollWheel:(NSEvent *)theEvent;
{
if (theEvent.deltaY < 0)
{
self.player.currentTime = self.player.currentTime + 0.5;
}
else if (theEvent.deltaY > 0)
{
self.player.currentTime = self.player.currentTime - 0.5;
}
return YES;
}
@end
@implementation HBPlayerHUDController (TouchBar)
static NSTouchBarItemIdentifier HBTouchBar = @"fr.handbrake.playerHUDTouchBar";
static NSTouchBarItemIdentifier HBTouchBarDone = @"fr.handbrake.done";
static NSTouchBarItemIdentifier HBTouchBarPlayPause = @"fr.handbrake.playPause";
static NSTouchBarItemIdentifier HBTouchBarCurrentTime = @"fr.handbrake.currentTime";
static NSTouchBarItemIdentifier HBTouchBarRemainingTime = @"fr.handbrake.remainingTime";
static NSTouchBarItemIdentifier HBTouchBarTimeSlider = @"fr.handbrake.timeSlider";
@dynamic touchBar;
- (NSTouchBar *)makeTouchBar
{
NSTouchBar *bar = [[NSTouchBar alloc] init];
bar.delegate = self;
bar.escapeKeyReplacementItemIdentifier = HBTouchBarDone;
bar.defaultItemIdentifiers = @[HBTouchBarPlayPause, NSTouchBarItemIdentifierFixedSpaceSmall, HBTouchBarCurrentTime, NSTouchBarItemIdentifierFixedSpaceSmall, HBTouchBarTimeSlider, NSTouchBarItemIdentifierFixedSpaceSmall, HBTouchBarRemainingTime];
bar.customizationIdentifier = HBTouchBar;
bar.customizationAllowedItemIdentifiers = @[HBTouchBarPlayPause, HBTouchBarCurrentTime, HBTouchBarTimeSlider, HBTouchBarRemainingTime, NSTouchBarItemIdentifierFixedSpaceSmall, NSTouchBarItemIdentifierFixedSpaceLarge, NSTouchBarItemIdentifierFlexibleSpace];
return bar;
}
- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
if ([identifier isEqualTo:HBTouchBarDone])
{
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Done", @"Touch bar");
NSButton *button = [NSButton buttonWithTitle:NSLocalizedString(@"Done", @"Touch bar") target:self action:@selector(showPicturesPreview:)];
button.bezelColor = NSColor.systemYellowColor;
item.view = button;
return item;
}
else if ([identifier isEqualTo:HBTouchBarPlayPause])
{
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Play/Pause", @"Touch bar");
NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate]
target:self action:@selector(playPauseToggle:)];
item.view = button;
return item;
}
else if ([identifier isEqualTo:HBTouchBarCurrentTime])
{
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Current Time", @"Touch bar");
NSTextField *label = [NSTextField labelWithString:NSLocalizedString(@"--:--", @"")];
item.view = label;
return item;
}
else if ([identifier isEqualTo:HBTouchBarRemainingTime])
{
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Remaining Time", @"Touch bar");
NSTextField *label = [NSTextField labelWithString:NSLocalizedString(@"- --:--", @"")];
item.view = label;
return item;
}
else if ([identifier isEqualTo:HBTouchBarTimeSlider])
{
NSSliderTouchBarItem *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Slider", @"Touch bar");
item.slider.minValue = 0.0f;
item.slider.maxValue = 100.0f;
item.slider.doubleValue = 0.0f;
item.slider.continuous = YES;
item.target = self;
item.action = @selector(touchBarSliderChanged:);
return item;
}
return nil;
}
- (void)touchBarSliderChanged:(NSSliderTouchBarItem *)sender
{
[self sliderChanged:sender.slider];
}
- (void)_touchBar_updatePlayState:(BOOL)playing
{
NSButton *playButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPlayPause] view];
if (playing)
{
playButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate];
}
else
{
playButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate];
}
}
- (void)_touchBar_updateMaxDuration:(NSTimeInterval)duration
{
NSSlider *slider = (NSSlider *)[[self.touchBar itemForIdentifier:HBTouchBarTimeSlider] slider];
slider.maxValue = duration;
}
- (NSString *)_timeToString:(NSTimeInterval)timeInSeconds negative:(BOOL)negative;
{
UInt16 seconds = (UInt16)fmod(timeInSeconds, 60.0);
UInt16 minutes = (UInt16)fmod(timeInSeconds / 60.0, 60.0);
if (negative)
{
return [NSString stringWithFormat:@"-%02d:%02d", minutes, seconds];
}
else
{
return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
}
}
- (NSAttributedString *)_monospacedString:(NSString *)string
{
return [[NSAttributedString alloc] initWithString:string attributes:self.normalMonospacedAttr];
}
- (void)_touchBar_updateTime:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration
{
NSSlider *slider = (NSSlider *)[[self.touchBar itemForIdentifier:HBTouchBarTimeSlider] slider];
NSTextField *currentTimeLabel = (NSTextField *)[[self.touchBar itemForIdentifier:HBTouchBarCurrentTime] view];
NSTextField *remainingTimeLabel = (NSTextField *)[[self.touchBar itemForIdentifier:HBTouchBarRemainingTime] view];
slider.doubleValue = currentTime;
currentTimeLabel.attributedStringValue = [self _monospacedString:[self _timeToString:currentTime negative:NO]];
remainingTimeLabel.attributedStringValue = [self _monospacedString:[self _timeToString:duration - currentTime negative:YES]];
}
@end