mpv/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m

305 lines
8.7 KiB
Objective-C

// 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 <mpv/client.h>
#import <mpv/opengl_cb.h>
#import <stdio.h>
#import <stdlib.h>
#import <OpenGL/gl.h>
#import <Cocoa/Cocoa.h>
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)fillBlack;
@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];
self.mpvGL = nil;
}
return self;
}
- (void)fillBlack
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
- (void)drawRect
{
if (self.mpvGL)
mpv_opengl_cb_draw(self.mpvGL, 0, self.bounds.size.width, -self.bounds.size.height);
else
[self fillBlack];
[[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 <NSApplicationDelegate>
{
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;
}