summaryrefslogtreecommitdiffstats
path: root/osdep/macosx_touchbar.m
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /osdep/macosx_touchbar.m
parentInitial commit. (diff)
downloadmpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz
mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'osdep/macosx_touchbar.m')
-rw-r--r--osdep/macosx_touchbar.m334
1 files changed, 334 insertions, 0 deletions
diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m
new file mode 100644
index 0000000..ccce8f7
--- /dev/null
+++ b/osdep/macosx_touchbar.m
@@ -0,0 +1,334 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "player/client.h"
+#import "macosx_touchbar.h"
+
+@implementation TouchBar
+
+@synthesize app = _app;
+@synthesize touchbarItems = _touchbar_items;
+@synthesize duration = _duration;
+@synthesize position = _position;
+@synthesize pause = _pause;
+
+- (id)init
+{
+ if (self = [super init]) {
+ self.touchbarItems = @{
+ seekBar: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"slider",
+ @"name": @"Seek Bar",
+ @"cmd": @"seek %f absolute-percent"
+ }],
+ play: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Play Button",
+ @"cmd": @"cycle pause",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate],
+ @"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]
+ }],
+ previousItem: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Previous Playlist Item",
+ @"cmd": @"playlist-prev",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate]
+ }],
+ nextItem: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Next Playlist Item",
+ @"cmd": @"playlist-next",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate]
+ }],
+ previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Previous Chapter",
+ @"cmd": @"add chapter -1",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate]
+ }],
+ nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Next Chapter",
+ @"cmd": @"add chapter 1",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate]
+ }],
+ cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Cycle Audio",
+ @"cmd": @"cycle audio",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate]
+ }],
+ cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Cycle Subtitle",
+ @"cmd": @"cycle sub",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate]
+ }],
+ currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"text",
+ @"name": @"Current Position"
+ }],
+ timeLeft: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"text",
+ @"name": @"Time Left"
+ }]
+ };
+
+ [self addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ }
+ return self;
+}
+
+- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)];
+ slider.minValue = 0.0f;
+ slider.maxValue = 100.0f;
+ tbItem.view = slider;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:slider forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSImage *tbImage = self.touchbarItems[identifier][@"image"];
+ NSButton *tbButton = [NSButton buttonWithImage:tbImage target:self action:@selector(buttonAction:)];
+ tbItem.view = tbButton;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:tbButton forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSTextField *tbText = [NSTextField labelWithString:@"0:00"];
+ tbText.alignment = NSTextAlignmentCenter;
+ tbItem.view = tbText;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:tbText forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ }
+
+ return nil;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary<NSKeyValueChangeKey,id> *)change
+ context:(void *)context {
+ if ([keyPath isEqualToString:@"visible"]) {
+ NSNumber *visible = [object valueForKey:@"visible"];
+ if (visible.boolValue) {
+ [self updateTouchBarTimeItems];
+ [self updatePlayButton];
+ }
+ }
+}
+
+- (void)updateTouchBarTimeItems
+{
+ if (!self.isVisible)
+ return;
+
+ [self updateSlider];
+ [self updateTimeLeft];
+ [self updateCurrentPosition];
+}
+
+- (void)updateSlider
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
+
+ if (self.duration <= 0) {
+ seekSlider.enabled = NO;
+ seekSlider.doubleValue = 0;
+ } else {
+ seekSlider.enabled = YES;
+ if (!seekSlider.highlighted)
+ seekSlider.doubleValue = (self.position/self.duration)*100;
+ }
+}
+
+- (void)updateTimeLeft
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
+
+ [self removeConstraintForIdentifier:timeLeft];
+ if (self.duration <= 0) {
+ timeLeftItem.stringValue = @"";
+ } else {
+ int left = (int)(floor(self.duration)-floor(self.position));
+ NSString *leftFormat = [self formatTime:left];
+ NSString *durFormat = [self formatTime:self.duration];
+ timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
+ [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
+ forIdentifier:timeLeft];
+ }
+}
+
+- (void)updateCurrentPosition
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
+ NSString *posFormat = [self formatTime:(int)floor(self.position)];
+ curPosItem.stringValue = posFormat;
+
+ [self removeConstraintForIdentifier:currentPosition];
+ if (self.duration <= 0) {
+ [self applyConstraintFromString:[self formatTime:self.position]
+ forIdentifier:currentPosition];
+ } else {
+ NSString *durFormat = [self formatTime:self.duration];
+ [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
+ }
+}
+
+- (void)updatePlayButton
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"];
+ if (!self.isVisible || !tbItem.visible)
+ return;
+
+ NSButton *playButton = self.touchbarItems[play][@"view"];
+ if (self.pause) {
+ playButton.image = self.touchbarItems[play][@"imageAlt"];
+ } else {
+ playButton.image = self.touchbarItems[play][@"image"];
+ }
+}
+
+- (void)buttonAction:(NSButton *)sender
+{
+ NSString *identifier = [self getIdentifierFromView:sender];
+ [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
+}
+
+- (void)seekbarChanged:(NSSlider *)slider
+{
+ NSString *identifier = [self getIdentifierFromView:slider];
+ NSString *seek = [NSString stringWithFormat:
+ self.touchbarItems[identifier][@"cmd"], slider.doubleValue];
+ [self.app queueCommand:(char *)[seek UTF8String]];
+}
+
+- (NSString *)formatTime:(int)time
+{
+ int seconds = time % 60;
+ int minutes = (time / 60) % 60;
+ int hours = time / (60 * 60);
+
+ NSString *stime = hours > 0 ? [NSString stringWithFormat:@"%d:", hours] : @"";
+ stime = (stime.length > 0 || minutes > 9) ?
+ [NSString stringWithFormat:@"%@%02d:", stime, minutes] :
+ [NSString stringWithFormat:@"%d:", minutes];
+ stime = [NSString stringWithFormat:@"%@%02d", stime, seconds];
+
+ return stime;
+}
+
+- (void)removeConstraintForIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ NSTextField *field = self.touchbarItems[identifier][@"view"];
+ [field removeConstraint:self.touchbarItems[identifier][@"constrain"]];
+}
+
+- (void)applyConstraintFromString:(NSString *)string
+ forIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ NSTextField *field = self.touchbarItems[identifier][@"view"];
+ if (field) {
+ NSString *fString = [[string componentsSeparatedByCharactersInSet:
+ [NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"];
+ NSTextField *textField = [NSTextField labelWithString:fString];
+ NSSize size = [textField frame].size;
+
+ NSLayoutConstraint *con =
+ [NSLayoutConstraint constraintWithItem:field
+ attribute:NSLayoutAttributeWidth
+ relatedBy:NSLayoutRelationEqual
+ toItem:nil
+ attribute:NSLayoutAttributeNotAnAttribute
+ multiplier:1.0
+ constant:(int)ceil(size.width*1.1)];
+ [field addConstraint:con];
+ [self.touchbarItems[identifier] setObject:con forKey:@"constrain"];
+ }
+}
+
+- (NSString *)getIdentifierFromView:(id)view
+{
+ NSString *identifier;
+ for (identifier in self.touchbarItems)
+ if([self.touchbarItems[identifier][@"view"] isEqual:view])
+ break;
+ return identifier;
+}
+
+- (void)processEvent:(struct mpv_event *)event
+{
+ switch (event->event_id) {
+ case MPV_EVENT_END_FILE: {
+ self.position = 0;
+ self.duration = 0;
+ break;
+ }
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ [self handlePropertyChange:(mpv_event_property *)event->data];
+ break;
+ }
+ }
+}
+
+- (void)handlePropertyChange:(struct mpv_event_property *)property
+{
+ NSString *name = [NSString stringWithUTF8String:property->name];
+ mpv_format format = property->format;
+
+ if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
+ double newPosition = *(double *)property->data;
+ newPosition = newPosition < 0 ? 0 : newPosition;
+ if ((int)(floor(newPosition) - floor(self.position)) != 0) {
+ self.position = newPosition;
+ [self updateTouchBarTimeItems];
+ }
+ } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
+ self.duration = *(double *)property->data;
+ [self updateTouchBarTimeItems];
+ } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
+ self.pause = *(int *)property->data;
+ [self updatePlayButton];
+ }
+}
+
+@end