summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2016-03-10 21:45:48 +0100
committerwm4 <wm4@nowhere>2016-03-10 21:45:48 +0100
commit4d7638ab0f2f841de4992ca519d6556e5b650a44 (patch)
tree89c4e112b4cef52c8279b1bf3b8b93e39bc7d31a
downloadmpv-examples-4d7638ab0f2f841de4992ca519d6556e5b650a44.tar.bz2
mpv-examples-4d7638ab0f2f841de4992ca519d6556e5b650a44.tar.xz
Reimport client API examples from mpv main repository
From commit d8b27ee4de4e26d594855960a4421b99db9e76ea.
-rw-r--r--libmpv/Copyright14
-rw-r--r--libmpv/README.md114
-rw-r--r--libmpv/cocoa-openglcb/cocoa-openglcb.m304
-rw-r--r--libmpv/cocoa/cocoabasic.m209
-rw-r--r--libmpv/qml/main.cpp136
-rw-r--r--libmpv/qml/main.h36
-rw-r--r--libmpv/qml/main.qml71
-rw-r--r--libmpv/qml/mpvtest.pro12
-rw-r--r--libmpv/qml/mpvtest.qrc5
-rw-r--r--libmpv/qml_direct/main.cpp159
-rw-r--r--libmpv/qml_direct/main.h51
-rw-r--r--libmpv/qml_direct/main.qml49
-rw-r--r--libmpv/qml_direct/mpvtest.pro12
-rw-r--r--libmpv/qml_direct/mpvtest.qrc5
-rw-r--r--libmpv/qt/qtexample.cpp225
-rw-r--r--libmpv/qt/qtexample.h37
-rw-r--r--libmpv/qt/qtexample.pro13
-rw-r--r--libmpv/qt_opengl/main.cpp13
-rw-r--r--libmpv/qt_opengl/mainwindow.cpp52
-rw-r--r--libmpv/qt_opengl/mainwindow.h27
-rw-r--r--libmpv/qt_opengl/mpvwidget.cpp129
-rw-r--r--libmpv/qt_opengl/mpvwidget.h38
-rw-r--r--libmpv/qt_opengl/qt_opengl.pro13
-rw-r--r--libmpv/sdl/main.c158
-rw-r--r--libmpv/simple/simple.c54
25 files changed, 1936 insertions, 0 deletions
diff --git a/libmpv/Copyright b/libmpv/Copyright
new file mode 100644
index 0000000..38d1891
--- /dev/null
+++ b/libmpv/Copyright
@@ -0,0 +1,14 @@
+All examples in this directory and its sub-directories are licensed
+under one of the following licenses:
+
+ WTFPL, ISC, Ms-PL, AGPLv3, BSD (any)
+
+Pick any license of your liking, and disregard the others.
+
+(The full text of each license is available on this website:
+ http://opensource.org/licenses/alphabetical )
+
+Additionally, you may consider the example code to be public domain.
+You are free to use any of the example code without further
+requirements or need for attribution.
+
diff --git a/libmpv/README.md b/libmpv/README.md
new file mode 100644
index 0000000..c21deb5
--- /dev/null
+++ b/libmpv/README.md
@@ -0,0 +1,114 @@
+# Client API examples
+
+All these examples use the mpv client API through libmpv.
+
+## Where are the docs?
+
+The libmpv C API is documented directly in the header files (on normal Unix
+systems, this is in `/usr/include/mpv/client.h`.
+
+libmpv merely gives you access to mpv's command interface, which is documented
+here:
+* Options (`mpv_set_option()` and friends): http://mpv.io/manual/master/#options
+* Commands (`mpv_command()` and friends): http://mpv.io/manual/master/#list-of-input-commands
+* Properties (`mpv_set_property()` and friends): http://mpv.io/manual/master/#properties
+
+Essentially everything is done with them, including loading a file, retrieving
+playback progress, and so on.
+
+## Methods of embedding the video window
+
+All of these examples concentrate on how to integrate mpv's video renderers
+with your own GUI. This is generally the hardest part. libmpv enforces a
+somewhat efficient video output method, rather than e.g. returning a RGBA
+surface in memory for each frame. The latter would be prohibitively inefficient,
+because it would require conversion on the CPU. The goal is also not requiring
+the API users to reinvent their own video rendering/scaling/presentation
+mechanisms.
+
+There are currently 2 methods of embedding video.
+
+### Native window embedding
+
+This uses the platform's native method of nesting multiple windows. For example,
+Linux/X11 can nest a window from a completely different process. The nested
+window can redraw contents on its own, and receive user input if the user
+interacts with this window.
+
+libmpv lets you specify a parent window for its own video window via the `wid`
+option. Then libmpv will create its window with your window as parent, and
+render its video inside of it.
+
+This method is highly OS-dependent. Some behavior is OS-specific. There are
+problems with focusing on X11 (the ancient X11 focus policy mismatches with
+that of modern UI toolkits - it's normally worked around, but this is not
+easily possible with raw window embedding). It seems to have stability problems
+on OSX when using the Qt toolkit.
+
+### OpenGL embedding
+
+This method lets you use libmpv's OpenGL renderer directly. You create an
+OpenGL context, and then use `mpv_opengl_cb_draw()` to render the video on
+each frame.
+
+This is more complicated, because libmpv will work directly on your own OpenGL
+state. It's also not possible to have mpv automatically receive user input.
+You will have to simulate this with the `mouse`/`keypress`/`keydown`/`keyup`
+commands.
+
+You also get much more flexibility. For example, you can actually render your
+own OSD on top of the video, something that is not possible with raw window
+embedding.
+
+### Which one to use?
+
+Due to the various platform-specific behavior and problems (in particular on
+OSX), OpenGL embedding is recommended. If you're not comfortable with requiring
+OpenGL, or want to support "direct" video output such as vdpau (which might
+win when it comes to performance and energy-saving), you should probably
+support both methods if possible.
+
+## List of examples
+
+### simple
+
+Very primitive terminal-only example. Shows some most basic API usage.
+
+### cocoa
+
+Shows how to embed the mpv video window in Objective-C/Cocoa.
+
+### cocoa-openglcb
+
+Similar to the cocoa sample, but shows how to integrate mpv's OpenGL renderer
+using libmpv's opengl-cb API. Since it does not require complicated interaction
+with Cocoa elements from different libraries, it's more robust.
+
+### qt
+
+Shows how to embed the mpv video window in Qt (using normal desktop widgets).
+
+### qt_opengl
+
+Shows how to use mpv's OpenGL video renderer in Qt. This uses the opengl-cb API
+for video. Since it does not rely on embedding "foreign" native Windows, it's
+usually more robust, potentially faster, and it's easier to control how your
+GUI interacts with the video. You can do your own OpenGL rendering on top of
+the video as well.
+
+### qml
+
+Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML. Uses the
+opengl-cb API for video. Since the video is a normal QML element, it's trivial
+to create OSD overlays with QML-native graphical elements as well.
+
+### qml_direct
+
+Alternative example, which typically avoids a FBO indirection. Might be
+slightly faster, but is less flexible and harder to use. In particular, the
+video is not a normal QML element. Uses the opengl-cb API for video.
+
+### sdl
+
+Show how to embed the mpv OpenGL renderer in SDL. Uses the opengl-cb API for
+video.
diff --git a/libmpv/cocoa-openglcb/cocoa-openglcb.m b/libmpv/cocoa-openglcb/cocoa-openglcb.m
new file mode 100644
index 0000000..4a4e8aa
--- /dev/null
+++ b/libmpv/cocoa-openglcb/cocoa-openglcb.m
@@ -0,0 +1,304 @@
+// 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;
+}
diff --git a/libmpv/cocoa/cocoabasic.m b/libmpv/cocoa/cocoabasic.m
new file mode 100644
index 0000000..5a78250
--- /dev/null
+++ b/libmpv/cocoa/cocoabasic.m
@@ -0,0 +1,209 @@
+// Plays a video from the command line in a view provided by the client
+// application.
+
+// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv` -framework cocoa
+
+#include <mpv/client.h>
+
+#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);
+ }
+}
+
+#import <Cocoa/Cocoa.h>
+
+@interface CocoaWindow : NSWindow
+@end
+
+@implementation CocoaWindow
+- (BOOL)canBecomeMainWindow { return YES; }
+- (BOOL)canBecomeKeyWindow { return YES; }
+@end
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+{
+ mpv_handle *mpv;
+ dispatch_queue_t queue;
+ NSWindow *w;
+ NSView *wrapper;
+}
+@end
+
+static void wakeup(void *);
+
+@implementation AppDelegate
+
+- (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"];
+ [self->w makeMainWindow];
+ [self->w makeKeyAndOrderFront:nil];
+
+ NSRect frame = [[self->w contentView] bounds];
+ self->wrapper = [[NSView alloc] initWithFrame:frame];
+ [self->wrapper setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+ [[self->w contentView] addSubview:self->wrapper];
+ [self->wrapper release];
+
+ 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: @"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"];
+ [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];
+
+ // Deal with MPV in the background.
+ queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
+ dispatch_async(queue, ^{
+
+ mpv = mpv_create();
+ if (!mpv) {
+ printf("failed creating context\n");
+ exit(1);
+ }
+
+ int64_t wid = (intptr_t) self->wrapper;
+ check_error(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid));
+
+ // 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"));
+
+ // for testing!
+ check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
+ check_error(mpv_set_option_string(mpv, "input-cursor", "no"));
+ check_error(mpv_set_option_string(mpv, "input-vo-keyboard", "yes"));
+
+ // request important errors
+ check_error(mpv_request_log_messages(mpv, "warn"));
+
+ check_error(mpv_initialize(mpv));
+
+ // 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));
+ });
+}
+
+- (void) handleEvent:(mpv_event *)event
+{
+ switch (event->event_id) {
+ case MPV_EVENT_SHUTDOWN: {
+ mpv_detach_destroy(mpv);
+ 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);
+ }
+
+ case MPV_EVENT_VIDEO_RECONFIG: {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSArray *subviews = [self->wrapper subviews];
+ if ([subviews count] > 0) {
+ // mpv's events view
+ NSView *eview = [self->wrapper subviews][0];
+ [self->w makeFirstResponder:eview];
+ }
+ });
+ }
+
+ default:
+ printf("event: %s\n", mpv_event_name(event->event_id));
+ }
+}
+
+- (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];
+}
+
+// Ostensibly, mpv's window would be hooked up to this.
+- (BOOL) windowShouldClose:(id)sender
+{
+ return NO;
+}
+
+- (void) mpv_stop
+{
+ if (mpv) {
+ const char *args[] = {"stop", NULL};
+ mpv_command(mpv, args);
+ }
+}
+
+- (void) mpv_quit
+{
+ 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 {
+ NSApplication *app = [NSApplication sharedApplication];
+ AppDelegate *delegate = [AppDelegate new];
+ app.delegate = delegate;
+ [app run];
+ }
+ return 0;
+}
diff --git a/libmpv/qml/main.cpp b/libmpv/qml/main.cpp
new file mode 100644
index 0000000..2122cd8
--- /dev/null
+++ b/libmpv/qml/main.cpp
@@ -0,0 +1,136 @@
+#include "main.h"
+
+#include <stdexcept>
+#include <clocale>
+
+#include <QObject>
+#include <QtGlobal>
+#include <QOpenGLContext>
+#include <QGuiApplication>
+
+#include <QtGui/QOpenGLFramebufferObject>
+
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/QQuickView>
+
+class MpvRenderer : public QQuickFramebufferObject::Renderer
+{
+ static void *get_proc_address(void *ctx, const char *name) {
+ (void)ctx;
+ QOpenGLContext *glctx = QOpenGLContext::currentContext();
+ if (!glctx)
+ return NULL;
+ return (void *)glctx->getProcAddress(QByteArray(name));
+ }
+
+ mpv::qt::Handle mpv;
+ QQuickWindow *window;
+ mpv_opengl_cb_context *mpv_gl;
+public:
+ MpvRenderer(const MpvObject *obj)
+ : mpv(obj->mpv), window(obj->window()), mpv_gl(obj->mpv_gl)
+ {
+ int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
+ if (r < 0)
+ throw std::runtime_error("could not initialize OpenGL");
+ }
+
+ virtual ~MpvRenderer()
+ {
+ // Until this call is done, we need to make sure the player remains
+ // alive. This is done implicitly with the mpv::qt::Handle instance
+ // in this class.
+ mpv_opengl_cb_uninit_gl(mpv_gl);
+ }
+
+ void render()
+ {
+ QOpenGLFramebufferObject *fbo = framebufferObject();
+ window->resetOpenGLState();
+ mpv_opengl_cb_draw(mpv_gl, fbo->handle(), fbo->width(), fbo->height());
+ window->resetOpenGLState();
+ }
+};
+
+MpvObject::MpvObject(QQuickItem * parent)
+ : QQuickFramebufferObject(parent), mpv_gl(0)
+{
+ mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
+ if (!mpv)
+ throw std::runtime_error("could not create mpv context");
+
+ mpv_set_option_string(mpv, "terminal", "yes");
+ mpv_set_option_string(mpv, "msg-level", "all=v");
+
+ if (mpv_initialize(mpv) < 0)
+ throw std::runtime_error("could not initialize mpv context");
+
+ // Make use of the MPV_SUB_API_OPENGL_CB API.
+ mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
+
+ // Request hw decoding, just for testing.
+ mpv::qt::set_option_variant(mpv, "hwdec", "auto");
+
+ // Setup the callback that will make QtQuick update and redraw if there
+ // is a new video frame. Use a queued connection: this makes sure the
+ // doUpdate() function is run on the GUI thread.
+ mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
+ if (!mpv_gl)
+ throw std::runtime_error("OpenGL not compiled in");
+ mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this);
+ connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate,
+ Qt::QueuedConnection);
+}
+
+MpvObject::~MpvObject()
+{
+ if (mpv_gl)
+ mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
+}
+
+void MpvObject::on_update(void *ctx)
+{
+ MpvObject *self = (MpvObject *)ctx;
+ emit self->onUpdate();
+}
+
+// connected to onUpdate(); signal makes sure it runs on the GUI thread
+void MpvObject::doUpdate()
+{
+ update();
+}
+
+void MpvObject::command(const QVariant& params)
+{
+ mpv::qt::command_variant(mpv, params);
+}
+
+void MpvObject::setProperty(const QString& name, const QVariant& value)
+{
+ mpv::qt::set_property_variant(mpv, name, value);
+}
+
+QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const
+{
+ window()->setPersistentOpenGLContext(true);
+ window()->setPersistentSceneGraph(true);
+ return new MpvRenderer(this);
+}
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ // Qt sets the locale in the QGuiApplication constructor, but libmpv
+ // requires the LC_NUMERIC category to be set to "C", so change it back.
+ std::setlocale(LC_NUMERIC, "C");
+
+ qmlRegisterType<MpvObject>("mpvtest", 1, 0, "MpvObject");
+
+ QQuickView view;
+ view.setResizeMode(QQuickView::SizeRootObjectToView);
+ view.setSource(QUrl("qrc:///mpvtest/main.qml"));
+ view.show();
+
+ return app.exec();
+}
diff --git a/libmpv/qml/main.h b/libmpv/qml/main.h
new file mode 100644
index 0000000..9a65ae5
--- /dev/null
+++ b/libmpv/qml/main.h
@@ -0,0 +1,36 @@
+#ifndef MPVRENDERER_H_
+#define MPVRENDERER_H_
+
+#include <QtQuick/QQuickFramebufferObject>
+
+#include <mpv/client.h>
+#include <mpv/opengl_cb.h>
+#include <mpv/qthelper.hpp>
+
+class MpvRenderer;
+
+class MpvObject : public QQuickFramebufferObject
+{
+ Q_OBJECT
+
+ mpv::qt::Handle mpv;
+ mpv_opengl_cb_context *mpv_gl;
+
+ friend class MpvRenderer;
+
+public:
+ MpvObject(QQuickItem * parent = 0);
+ virtual ~MpvObject();
+ virtual Renderer *createRenderer() const;
+public slots:
+ void command(const QVariant& params);
+ void setProperty(const QString& name, const QVariant& value);
+signals:
+ void onUpdate();
+private slots:
+ void doUpdate();
+private:
+ static void on_update(void *ctx);
+};
+
+#endif
diff --git a/libmpv/qml/main.qml b/libmpv/qml/main.qml
new file mode 100644
index 0000000..ec06790
--- /dev/null
+++ b/libmpv/qml/main.qml
@@ -0,0 +1,71 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.0
+
+import mpvtest 1.0
+
+Item {
+ width: 1280
+ height: 720
+
+ MpvObject {
+ id: renderer
+ anchors.fill: parent
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: renderer.command(["loadfile", "../../../test.mkv"])
+ }
+ }
+
+ Rectangle {
+ id: labelFrame
+ anchors.margins: -50
+ radius: 5
+ color: "white"
+ border.color: "black"
+ opacity: 0.8
+ anchors.fill: box
+ }
+
+ Row {
+ id: box
+ anchors.bottom: renderer.bottom
+ anchors.left: renderer.left
+ anchors.right: renderer.right
+ anchors.margins: 100
+
+ Text {
+ anchors.margins: 10
+ wrapMode: Text.WordWrap
+ text: "QtQuick and mpv are both rendering stuff.\n
+ Click to load ../../../test.mkv"
+ }
+
+ // Don't take these controls too seriously. They're for testing.
+ Column {
+ CheckBox {
+ id: checkbox
+ anchors.margins: 10
+ // Heavily filtered means good, right?
+ text: "Make video look like on a Smart TV"
+ onClicked: {
+ if (checkbox.checked) {
+ renderer.command(["vo_cmdline", "sharpen=5.0"])
+ } else {
+ renderer.command(["vo_cmdline", ""])
+ }
+ }
+ }
+ Slider {
+ id: slider
+ anchors.margins: 10
+ anchors.left: checkbox.left
+ anchors.right: checkbox.right
+ minimumValue: -100
+ maximumValue: 100
+ value: 0
+ onValueChanged: renderer.setProperty("gamma", slider.value | 0)
+ }
+ }
+ }
+}
diff --git a/libmpv/qml/mpvtest.pro b/libmpv/qml/mpvtest.pro
new file mode 100644
index 0000000..0b2e96f
--- /dev/null
+++ b/libmpv/qml/mpvtest.pro
@@ -0,0 +1,12 @@
+QT += qml quick
+
+HEADERS += main.h
+SOURCES += main.cpp