From 83012fd77ab32c7eb0f42491d4ed95c9d9805a55 Mon Sep 17 00:00:00 2001 From: torque Date: Wed, 20 May 2015 20:53:55 -0700 Subject: DOCS/client_api_examples: add opengl-cb-based cocoa example. Contains a basic toggle button that pauses the video. --- .../cocoa-openglcb/cocoa-openglcb.m | 310 +++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m (limited to 'DOCS/client_api_examples') diff --git a/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m b/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m new file mode 100644 index 0000000000..f65c6a3664 --- /dev/null +++ b/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m @@ -0,0 +1,310 @@ +// Plays a video from the command line in an opengl view in its own window. + +// Build with: clang -o cocoa-openglcb cocoa-openglcb.m `pkg-config --libs --cflags mpv` -framework Cocoa -framework OpenGL + +#import +#import + +#import +#import +#import + +#import + + +static inline void check_error(int status) +{ + if (status < 0) { + printf("mpv API error: %s\n", mpv_error_string(status)); + exit(1); + } +} + +static void *get_proc_address(void *ctx, const char *name) +{ + CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII); + void *addr = CFBundleGetFunctionPointerForName(CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")), symbolName); + CFRelease(symbolName); + return addr; +} + +static void glupdate(void *ctx); + +@interface MpvClientOGLView : NSOpenGLView +@property mpv_opengl_cb_context *mpvGL; +- (instancetype)initWithFrame:(NSRect)frame; +- (void)drawRect; +- (void)flushBlack; +@end + +@implementation MpvClientOGLView +- (instancetype)initWithFrame:(NSRect)frame +{ + // make sure the pixel format is double buffered so we can use + // [[self openGLContext] flushBuffer]. + NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPFADoubleBuffer, + 0 + }; + self = [super initWithFrame:frame + pixelFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]]; + + if (self) { + [self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + // swap on vsyncs + GLint swapInt = 1; + [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; + [[self openGLContext] makeCurrentContext]; + } + return self; +} + +- (void)flushBlack +{ + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + [[self openGLContext] flushBuffer]; + [[self openGLContext] flushBuffer]; +} + +- (void)prepareOpenGL +{ + [super prepareOpenGL]; + [self flushBlack]; + self.mpvGL = nil; +} + +- (void)drawRect +{ + if (self.mpvGL) + mpv_opengl_cb_draw(self.mpvGL, 0, self.bounds.size.width, -self.bounds.size.height); + [[self openGLContext] flushBuffer]; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [self drawRect]; +} +@end + +@interface CocoaWindow : NSWindow +@property(retain, readonly) MpvClientOGLView *glView; +@property(retain, readonly) NSButton *pauseButton; +@end + +@implementation CocoaWindow +- (BOOL)canBecomeMainWindow { return YES; } +- (BOOL)canBecomeKeyWindow { return YES; } +- (void)initOGLView { + NSRect bounds = [[self contentView] bounds]; + // window coordinate origin is bottom left + NSRect glFrame = NSMakeRect(bounds.origin.x, bounds.origin.y + 30, bounds.size.width, bounds.size.height - 30); + _glView = [[MpvClientOGLView alloc] initWithFrame:glFrame]; + [self.contentView addSubview:_glView]; + + NSRect buttonFrame = NSMakeRect(bounds.origin.x, bounds.origin.y, 60, 30); + _pauseButton = [[NSButton alloc] initWithFrame:buttonFrame]; + _pauseButton.buttonType = NSToggleButton; + // button target has to be the delegate (it holds the mpv context + // pointer), so that's set later. + _pauseButton.action = @selector(togglePause:); + _pauseButton.title = @"Pause"; + _pauseButton.alternateTitle = @"Play"; + [self.contentView addSubview:_pauseButton]; +} +@end + +@interface AppDelegate : NSObject +{ + mpv_handle *mpv; + dispatch_queue_t queue; + CocoaWindow *window; +} +@end + +static void wakeup(void *); + +@implementation AppDelegate + +- (void)createWindow { + + int mask = NSTitledWindowMask|NSClosableWindowMask| + NSMiniaturizableWindowMask|NSResizableWindowMask; + + window = [[CocoaWindow alloc] + initWithContentRect:NSMakeRect(0, 0, 1280, 720) + styleMask:mask + backing:NSBackingStoreBuffered + defer:NO]; + + // force a minimum size to stop opengl from exploding. + [window setMinSize:NSMakeSize(200, 200)]; + [window initOGLView]; + [window setTitle:@"cocoa-openglcb example"]; + [window makeMainWindow]; + [window makeKeyAndOrderFront:nil]; + + NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"]; + NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""]; + NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"]; + [m setSubmenu:sm forItem:item]; + [sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"]; + [NSApp setMenu:m]; + [NSApp activateIgnoringOtherApps:YES]; +} + +- (void) applicationDidFinishLaunching:(NSNotification *)notification { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + atexit_b(^{ + // Because activation policy has just been set to behave like a real + // application, that policy must be reset on exit to prevent, among + // other things, the menubar created here from remaining on screen. + [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited]; + }); + + // Read filename + NSArray *args = [NSProcessInfo processInfo].arguments; + if (args.count < 2) { + NSLog(@"Expected filename on command line"); + exit(1); + } + NSString *filename = args[1]; + + [self createWindow]; + window.pauseButton.target = self; + + mpv = mpv_create(); + if (!mpv) { + printf("failed creating context\n"); + exit(1); + } + + check_error(mpv_set_option_string(mpv, "input-media-keys", "yes")); + // request important errors + check_error(mpv_request_log_messages(mpv, "warn")); + + check_error(mpv_initialize(mpv)); + check_error(mpv_set_option_string(mpv, "vo", "opengl-cb")); + mpv_opengl_cb_context *mpvGL = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); + if (!mpvGL) { + puts("libmpv does not have the opengl-cb sub-API."); + exit(1); + } + // pass the mpvGL context to our view + window.glView.mpvGL = mpvGL; + int r = mpv_opengl_cb_init_gl(mpvGL, NULL, get_proc_address, NULL); + if (r < 0) { + puts("gl init has failed."); + exit(1); + } + mpv_opengl_cb_set_update_callback(mpvGL, glupdate, (__bridge void *)window.glView); + + // Deal with MPV in the background. + queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL); + dispatch_async(queue, ^{ + // Register to be woken up whenever mpv generates new events. + mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *)self); + // Load the indicated file + const char *cmd[] = {"loadfile", filename.UTF8String, NULL}; + check_error(mpv_command(mpv, cmd)); + }); +} + +static void glupdate(void *ctx) +{ + MpvClientOGLView *glView = (__bridge MpvClientOGLView *)ctx; + // I'm still not sure what the best way to handle this is, but this + // works. + dispatch_async(dispatch_get_main_queue(), ^{ + [glView drawRect]; + }); +} + +- (void) handleEvent:(mpv_event *)event +{ + switch (event->event_id) { + case MPV_EVENT_SHUTDOWN: { + mpv_detach_destroy(mpv); + mpv_opengl_cb_uninit_gl(window.glView.mpvGL); + mpv = NULL; + printf("event: shutdown\n"); + break; + } + + case MPV_EVENT_LOG_MESSAGE: { + struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data; + printf("[%s] %s: %s", msg->prefix, msg->level, msg->text); + } + + default: + printf("event: %s\n", mpv_event_name(event->event_id)); + } +} + +- (void)togglePause:(NSButton *)button +{ + if (mpv) { + switch (button.state) { + case NSOffState: + { + int pause = 0; + mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause); + } + break; + case NSOnState: + { + int pause = 1; + mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause); + } + break; + default: + NSLog(@"This should never happen."); + } + } +} + +- (void) readEvents +{ + dispatch_async(queue, ^{ + while (mpv) { + mpv_event *event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_NONE) + break; + [self handleEvent:event]; + } + }); +} + +static void wakeup(void *context) +{ + AppDelegate *a = (__bridge AppDelegate *) context; + [a readEvents]; +} + +// quit when the window is closed. +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app +{ + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + NSLog(@"Terminating."); + const char *args[] = {"quit", NULL}; + mpv_command(mpv, args); + [window.glView clearGLContext]; + return NSTerminateNow; +} + +@end + +// Delete this if you already have a main.m. +int main(int argc, const char * argv[]) { + @autoreleasepool { + NSApplication *app = [NSApplication sharedApplication]; + AppDelegate *delegate = [AppDelegate new]; + app.delegate = delegate; + [app run]; + } + return 0; +} -- cgit v1.2.3