2014-08-11 02:29:46 +00:00
|
|
|
// Plays a video from the command line in a window provided by mpv.
|
|
|
|
// You likely want to play the video in your own window instead,
|
|
|
|
// but that's not quite ready yet.
|
|
|
|
// You may need a basic Info.plist and MainMenu.xib to make this work.
|
|
|
|
|
2015-01-23 14:32:23 +00:00
|
|
|
// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv`
|
|
|
|
|
2015-01-01 22:56:41 +00:00
|
|
|
#include <mpv/client.h>
|
2015-01-01 22:07:46 +00:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
static inline void check_error(int status)
|
|
|
|
{
|
|
|
|
if (status < 0) {
|
|
|
|
printf("mpv API error: %s\n", mpv_error_string(status));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
2014-08-11 02:29:46 +00:00
|
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
|
2014-10-05 12:28:33 +00:00
|
|
|
@interface CocoaWindow : NSWindow
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation CocoaWindow
|
|
|
|
- (BOOL)canBecomeMainWindow { return YES; }
|
|
|
|
- (BOOL)canBecomeKeyWindow { return YES; }
|
|
|
|
@end
|
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
|
|
|
{
|
|
|
|
mpv_handle *mpv;
|
2014-08-11 08:49:50 +00:00
|
|
|
dispatch_queue_t queue;
|
2014-10-05 12:28:33 +00:00
|
|
|
NSWindow *w;
|
2014-08-11 02:29:46 +00:00
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
static void wakeup(void *);
|
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
@implementation AppDelegate
|
|
|
|
|
2014-10-05 12:28:33 +00:00
|
|
|
- (void)createWindow {
|
|
|
|
|
|
|
|
int mask = NSTitledWindowMask|NSClosableWindowMask|
|
|
|
|
NSMiniaturizableWindowMask|NSResizableWindowMask;
|
|
|
|
|
|
|
|
self->w = [[CocoaWindow alloc]
|
|
|
|
initWithContentRect:NSMakeRect(0,0, 1280, 720)
|
|
|
|
styleMask:mask
|
|
|
|
backing:NSBackingStoreBuffered
|
|
|
|
defer:NO];
|
|
|
|
|
|
|
|
[self->w setTitle:@"cocoabasic example"];
|
2014-10-11 22:17:48 +00:00
|
|
|
[self->w makeMainWindow];
|
2014-10-05 12:28:33 +00:00
|
|
|
[self->w makeKeyAndOrderFront:nil];
|
2014-10-27 18:10:09 +00:00
|
|
|
|
|
|
|
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];
|
2015-02-28 13:16:16 +00:00
|
|
|
[sm addItemWithTitle: @"mpv_command('stop')" action:@selector(mpv_stop) keyEquivalent:@""];
|
|
|
|
[sm addItemWithTitle: @"mpv_command('quit')" action:@selector(mpv_quit) keyEquivalent:@""];
|
|
|
|
[sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
|
2014-10-27 18:10:09 +00:00
|
|
|
[NSApp setMenu:m];
|
2014-10-05 12:28:33 +00:00
|
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
|
|
}
|
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
|
2014-10-05 07:31:12 +00:00
|
|
|
[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];
|
|
|
|
});
|
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
// Read filename
|
|
|
|
NSArray *args = [NSProcessInfo processInfo].arguments;
|
|
|
|
if (args.count < 2) {
|
|
|
|
NSLog(@"Expected filename on command line");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
NSString *filename = args[1];
|
|
|
|
|
2014-10-05 12:28:33 +00:00
|
|
|
[self createWindow];
|
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
// Deal with MPV in the background.
|
|
|
|
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
|
|
|
|
dispatch_async(queue, ^{
|
2014-08-11 02:29:46 +00:00
|
|
|
|
|
|
|
mpv = mpv_create();
|
|
|
|
if (!mpv) {
|
|
|
|
printf("failed creating context\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
2014-08-11 08:49:50 +00:00
|
|
|
|
2014-10-08 17:23:47 +00:00
|
|
|
int64_t wid = (intptr_t) [self->w contentView];
|
2014-10-05 12:28:33 +00:00
|
|
|
check_error(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid));
|
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
// Maybe set some options here, like default key bindings.
|
|
|
|
// NOTE: Interaction with the window seems to be broken for now.
|
|
|
|
check_error(mpv_set_option_string(mpv, "input-default-bindings", "yes"));
|
|
|
|
|
2014-10-09 19:02:47 +00:00
|
|
|
// for testing!
|
|
|
|
check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
|
2014-10-17 17:15:17 +00:00
|
|
|
check_error(mpv_set_option_string(mpv, "input-cursor", "no"));
|
2014-10-11 22:17:48 +00:00
|
|
|
check_error(mpv_set_option_string(mpv, "input-vo-keyboard", "yes"));
|
|
|
|
|
|
|
|
// request important errors
|
|
|
|
check_error(mpv_request_log_messages(mpv, "warn"));
|
2014-10-09 19:02:47 +00:00
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
check_error(mpv_initialize(mpv));
|
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
// Register to be woken up whenever mpv generates new events.
|
|
|
|
mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *) self);
|
|
|
|
|
|
|
|
// Load the indicated file
|
2014-08-11 02:29:46 +00:00
|
|
|
const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
|
|
|
|
check_error(mpv_command(mpv, cmd));
|
2014-08-11 08:49:50 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) handleEvent:(mpv_event *)event
|
|
|
|
{
|
|
|
|
switch (event->event_id) {
|
2015-02-28 13:16:16 +00:00
|
|
|
case MPV_EVENT_SHUTDOWN: {
|
|
|
|
mpv_detach_destroy(mpv);
|
2014-10-11 22:17:48 +00:00
|
|
|
mpv = NULL;
|
2015-02-28 13:16:16 +00:00
|
|
|
printf("event: shutdown\n");
|
2014-10-11 22:17:48 +00:00
|
|
|
break;
|
2015-02-28 13:16:16 +00:00
|
|
|
}
|
2014-10-11 22:17:48 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-02-28 13:16:16 +00:00
|
|
|
case MPV_EVENT_VIDEO_RECONFIG: {
|
2014-10-11 22:17:48 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self->w selectNextKeyView:nil];
|
|
|
|
});
|
2015-02-28 13:16:16 +00:00
|
|
|
}
|
2014-10-11 22:17:48 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
printf("event: %s\n", mpv_event_name(event->event_id));
|
2014-08-11 08:49:50 +00:00
|
|
|
}
|
|
|
|
}
|
2014-08-11 02:29:46 +00:00
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
- (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];
|
|
|
|
}
|
2014-08-11 02:29:46 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-08-11 08:49:50 +00:00
|
|
|
static void wakeup(void *context) {
|
|
|
|
AppDelegate *a = (__bridge AppDelegate *) context;
|
|
|
|
[a readEvents];
|
|
|
|
}
|
|
|
|
|
2014-08-11 02:29:46 +00:00
|
|
|
// Ostensibly, mpv's window would be hooked up to this.
|
|
|
|
- (BOOL) windowShouldClose:(id)sender
|
2014-08-11 08:49:50 +00:00
|
|
|
{
|
2015-02-28 13:16:16 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) mpv_stop
|
|
|
|
{
|
|
|
|
if (mpv) {
|
|
|
|
const char *args[] = {"stop", NULL};
|
|
|
|
mpv_command(mpv, args);
|
|
|
|
}
|
2014-08-11 08:49:50 +00:00
|
|
|
}
|
|
|
|
|
2015-02-28 13:16:16 +00:00
|
|
|
- (void) mpv_quit
|
2014-08-11 02:29:46 +00:00
|
|
|
{
|
|
|
|
if (mpv) {
|
|
|
|
const char *args[] = {"quit", NULL};
|
|
|
|
mpv_command(mpv, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
// Delete this if you already have a main.m.
|
|
|
|
int main(int argc, const char * argv[]) {
|
|
|
|
@autoreleasepool {
|
2014-10-05 08:06:46 +00:00
|
|
|
NSApplication *app = [NSApplication sharedApplication];
|
2014-08-11 02:29:46 +00:00
|
|
|
AppDelegate *delegate = [AppDelegate new];
|
|
|
|
app.delegate = delegate;
|
|
|
|
[app run];
|
|
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|