Merge branch 'master' into release/current
This commit is contained in:
165 files changed
+5426
-4648
No files matched your search
@@ -1,14 +0,0 @@
|
||||
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.
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,304 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#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
|
||||
@@ -1,71 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += qml quick
|
||||
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
RESOURCES += mpvtest.qrc
|
||||
|
||||
OTHER_FILES += main.qml
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/mpvtest">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,159 +0,0 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <clocale>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QtQuick/QQuickView>
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
MpvRenderer::MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl)
|
||||
: mpv(a_mpv), mpv_gl(a_mpv_gl), window(0), size()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
MpvRenderer::~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 MpvRenderer::paint()
|
||||
{
|
||||
window->resetOpenGLState();
|
||||
|
||||
// This uses 0 as framebuffer, which indicates that mpv will render directly
|
||||
// to the frontbuffer. Note that mpv will always switch framebuffers
|
||||
// explicitly. Some QWindow setups (such as using QQuickWidget) actually
|
||||
// want you to render into a FBO in the beforeRendering() signal, and this
|
||||
// code won't work there.
|
||||
// The negation is used for rendering with OpenGL's flipped coordinates.
|
||||
mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height());
|
||||
|
||||
window->resetOpenGLState();
|
||||
}
|
||||
|
||||
MpvObject::MpvObject(QQuickItem * parent)
|
||||
: QQuickItem(parent), mpv_gl(0), renderer(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");
|
||||
|
||||
// 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);
|
||||
|
||||
connect(this, &QQuickItem::windowChanged,
|
||||
this, &MpvObject::handleWindowChanged);
|
||||
}
|
||||
|
||||
MpvObject::~MpvObject()
|
||||
{
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
}
|
||||
|
||||
void MpvObject::handleWindowChanged(QQuickWindow *win)
|
||||
{
|
||||
if (!win)
|
||||
return;
|
||||
connect(win, &QQuickWindow::beforeSynchronizing,
|
||||
this, &MpvObject::sync, Qt::DirectConnection);
|
||||
connect(win, &QQuickWindow::sceneGraphInvalidated,
|
||||
this, &MpvObject::cleanup, Qt::DirectConnection);
|
||||
connect(win, &QQuickWindow::frameSwapped,
|
||||
this, &MpvObject::swapped, Qt::DirectConnection);
|
||||
win->setClearBeforeRendering(false);
|
||||
}
|
||||
|
||||
void MpvObject::sync()
|
||||
{
|
||||
if (!renderer) {
|
||||
renderer = new MpvRenderer(mpv, mpv_gl);
|
||||
connect(window(), &QQuickWindow::beforeRendering,
|
||||
renderer, &MpvRenderer::paint, Qt::DirectConnection);
|
||||
}
|
||||
renderer->window = window();
|
||||
renderer->size = window()->size() * window()->devicePixelRatio();
|
||||
}
|
||||
|
||||
void MpvObject::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvObject::cleanup()
|
||||
{
|
||||
if (renderer) {
|
||||
delete renderer;
|
||||
renderer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
window()->update();
|
||||
}
|
||||
|
||||
void MpvObject::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef MPVRENDERER_H_
|
||||
#define MPVRENDERER_H_
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvRenderer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
QQuickWindow *window;
|
||||
QSize size;
|
||||
|
||||
friend class MpvObject;
|
||||
public:
|
||||
MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl);
|
||||
virtual ~MpvRenderer();
|
||||
public slots:
|
||||
void paint();
|
||||
};
|
||||
|
||||
class MpvObject : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
MpvRenderer *renderer;
|
||||
|
||||
public:
|
||||
MpvObject(QQuickItem * parent = 0);
|
||||
virtual ~MpvObject();
|
||||
public slots:
|
||||
void command(const QVariant& params);
|
||||
void sync();
|
||||
void swapped();
|
||||
void cleanup();
|
||||
signals:
|
||||
void onUpdate();
|
||||
private slots:
|
||||
void doUpdate();
|
||||
void handleWindowChanged(QQuickWindow *win);
|
||||
private:
|
||||
static void on_update(void *ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,49 +0,0 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.0
|
||||
|
||||
import mpvtest 1.0
|
||||
|
||||
Item {
|
||||
width: 1280
|
||||
height: 720
|
||||
|
||||
MpvObject {
|
||||
id: renderer
|
||||
|
||||
// This object isn't real and not visible; it just renders into the
|
||||
// background of the containing Window.
|
||||
width: 0
|
||||
height: 0
|
||||
}
|
||||
|
||||
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: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 100
|
||||
|
||||
Text {
|
||||
anchors.margins: 10
|
||||
wrapMode: Text.WordWrap
|
||||
text: "QtQuick and mpv are both rendering stuff.\n
|
||||
In this example, mpv is always in the background.\n
|
||||
Click to load ../../../test.mkv"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += qml quick
|
||||
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
RESOURCES += mpvtest.qrc
|
||||
|
||||
OTHER_FILES += main.qml
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/mpvtest">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,225 +0,0 @@
|
||||
// This example can be built with: qmake && make
|
||||
|
||||
#include <clocale>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QFileDialog>
|
||||
#include <QStatusBar>
|
||||
#include <QMenuBar>
|
||||
#include <QMenu>
|
||||
#include <QGridLayout>
|
||||
#include <QApplication>
|
||||
#include <QTextEdit>
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
#include <QJsonDocument>
|
||||
#endif
|
||||
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
#include "qtexample.h"
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
// This callback is invoked from any mpv thread (but possibly also
|
||||
// recursively from a thread that is calling the mpv API). Just notify
|
||||
// the Qt GUI thread to wake up (so that it can process events with
|
||||
// mpv_wait_event()), and return as quickly as possible.
|
||||
MainWindow *mainwindow = (MainWindow *)ctx;
|
||||
emit mainwindow->mpv_events();
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
setWindowTitle("Qt embedding demo");
|
||||
setMinimumSize(640, 480);
|
||||
|
||||
QMenu *menu = menuBar()->addMenu(tr("&File"));
|
||||
QAction *on_open = new QAction(tr("&Open"), this);
|
||||
on_open->setShortcuts(QKeySequence::Open);
|
||||
on_open->setStatusTip(tr("Open a file"));
|
||||
connect(on_open, &QAction::triggered, this, &MainWindow::on_file_open);
|
||||
menu->addAction(on_open);
|
||||
|
||||
QAction *on_new = new QAction(tr("&New window"), this);
|
||||
connect(on_new, &QAction::triggered, this, &MainWindow::on_new_window);
|
||||
menu->addAction(on_new);
|
||||
|
||||
statusBar();
|
||||
|
||||
QMainWindow *log_window = new QMainWindow(this);
|
||||
log = new QTextEdit(log_window);
|
||||
log->setReadOnly(true);
|
||||
log_window->setCentralWidget(log);
|
||||
log_window->setWindowTitle("mpv log window");
|
||||
log_window->setMinimumSize(500, 50);
|
||||
log_window->show();
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv)
|
||||
throw std::runtime_error("can't create mpv instance");
|
||||
|
||||
// Create a video child window. Force Qt to create a native window, and
|
||||
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
|
||||
mpv_container = new QWidget(this);
|
||||
setCentralWidget(mpv_container);
|
||||
mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||
mpv_container->setAttribute(Qt::WA_NativeWindow);
|
||||
// If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
|
||||
int64_t wid = mpv_container->winId();
|
||||
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
|
||||
// Enable default bindings, because we're lazy. Normally, a player using
|
||||
// mpv as backend would implement its own key bindings.
|
||||
mpv_set_option_string(mpv, "input-default-bindings", "yes");
|
||||
|
||||
// Enable keyboard input on the X11 window. For the messy details, see
|
||||
// --input-vo-keyboard on the manpage.
|
||||
mpv_set_option_string(mpv, "input-vo-keyboard", "yes");
|
||||
|
||||
// Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
|
||||
// this property changes.
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
|
||||
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
||||
|
||||
// Request log messages with level "info" or higher.
|
||||
// They are received as MPV_EVENT_LOG_MESSAGE.
|
||||
mpv_request_log_messages(mpv, "info");
|
||||
|
||||
// From this point on, the wakeup function will be called. The callback
|
||||
// can come from any thread, so we use the QueuedConnection mechanism to
|
||||
// relay the wakeup in a thread-safe way.
|
||||
connect(this, &MainWindow::mpv_events, this, &MainWindow::on_mpv_events,
|
||||
Qt::QueuedConnection);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("mpv failed to initialize");
|
||||
}
|
||||
|
||||
void MainWindow::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
std::stringstream ss;
|
||||
ss << "At: " << time;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
} else if (prop->format == MPV_FORMAT_NONE) {
|
||||
// The property is unavailable, which probably means playback
|
||||
// was stopped.
|
||||
statusBar()->showMessage("");
|
||||
}
|
||||
} else if (strcmp(prop->name, "chapter-list") == 0 ||
|
||||
strcmp(prop->name, "track-list") == 0)
|
||||
{
|
||||
// Dump the properties as JSON for demo purposes.
|
||||
#if QT_VERSION >= 0x050000
|
||||
if (prop->format == MPV_FORMAT_NODE) {
|
||||
QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
|
||||
// Abuse JSON support for easily printing the mpv_node contents.
|
||||
QJsonDocument d = QJsonDocument::fromVariant(v);
|
||||
append_log("Change property " + QString(prop->name) + ":\n");
|
||||
append_log(d.toJson().data());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_VIDEO_RECONFIG: {
|
||||
// Retrieve the new video size.
|
||||
int64_t w, h;
|
||||
if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
|
||||
mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 &&
|
||||
w > 0 && h > 0)
|
||||
{
|
||||
// Note that the MPV_EVENT_VIDEO_RECONFIG event doesn't necessarily
|
||||
// imply a resize, and you should check yourself if the video
|
||||
// dimensions really changed.
|
||||
// mpv itself will scale/letter box the video to the container size
|
||||
// if the video doesn't fit.
|
||||
std::stringstream ss;
|
||||
ss << "Reconfig: " << w << " " << h;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
std::stringstream ss;
|
||||
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
|
||||
append_log(QString::fromStdString(ss.str()));
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_terminate_destroy(mpv);
|
||||
mpv = NULL;
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
// This slot is invoked by wakeup() (through the mpv_events signal).
|
||||
void MainWindow::on_mpv_events()
|
||||
{
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_file_open()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, "Open file");
|
||||
if (mpv) {
|
||||
const QByteArray c_filename = filename.toUtf8();
|
||||
const char *args[] = {"loadfile", c_filename.data(), NULL};
|
||||
mpv_command_async(mpv, 0, args);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_new_window()
|
||||
{
|
||||
(new MainWindow())->show();
|
||||
}
|
||||
|
||||
void MainWindow::append_log(const QString &text)
|
||||
{
|
||||
QTextCursor cursor = log->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text);
|
||||
log->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (mpv)
|
||||
mpv_terminate_destroy(mpv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef QTEXAMPLE_H
|
||||
#define QTEXAMPLE_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
class QTextEdit;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void on_file_open();
|
||||
void on_new_window();
|
||||
void on_mpv_events();
|
||||
|
||||
signals:
|
||||
void mpv_events();
|
||||
|
||||
private:
|
||||
QWidget *mpv_container;
|
||||
mpv_handle *mpv;
|
||||
QTextEdit *log;
|
||||
|
||||
void append_log(const QString &text);
|
||||
|
||||
void create_player();
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
};
|
||||
|
||||
#endif // QTEXAMPLE_H
|
||||
@@ -1,13 +0,0 @@
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qtexample
|
||||
TEMPLATE = app
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
SOURCES += qtexample.cpp
|
||||
HEADERS += qtexample.h
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <QApplication>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "mainwindow.h"
|
||||
#include "mpvwidget.h"
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QLayout>
|
||||
#include <QFileDialog>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
m_mpv = new MpvWidget(this);
|
||||
m_slider = new QSlider();
|
||||
m_slider->setOrientation(Qt::Horizontal);
|
||||
m_openBtn = new QPushButton("Open");
|
||||
m_playBtn = new QPushButton("Pause");
|
||||
QHBoxLayout *hb = new QHBoxLayout();
|
||||
hb->addWidget(m_openBtn);
|
||||
hb->addWidget(m_playBtn);
|
||||
QVBoxLayout *vl = new QVBoxLayout();
|
||||
vl->addWidget(m_mpv);
|
||||
vl->addWidget(m_slider);
|
||||
vl->addLayout(hb);
|
||||
setLayout(vl);
|
||||
connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int)));
|
||||
connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia()));
|
||||
connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume()));
|
||||
connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int)));
|
||||
connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int)));
|
||||
}
|
||||
|
||||
void MainWindow::openMedia()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(0, "Open a video");
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
m_mpv->command(QStringList() << "loadfile" << file);
|
||||
}
|
||||
|
||||
void MainWindow::seek(int pos)
|
||||
{
|
||||
m_mpv->command(QVariantList() << "seek" << pos << "absolute");
|
||||
}
|
||||
|
||||
void MainWindow::pauseResume()
|
||||
{
|
||||
const bool paused = m_mpv->getProperty("pause").toBool();
|
||||
m_mpv->setProperty("pause", !paused);
|
||||
}
|
||||
|
||||
void MainWindow::setSliderRange(int duration)
|
||||
{
|
||||
m_slider->setRange(0, duration);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef MainWindow_H
|
||||
#define MainWindow_H
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class MpvWidget;
|
||||
class QSlider;
|
||||
class QPushButton;
|
||||
class MainWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
public Q_SLOTS:
|
||||
void openMedia();
|
||||
void seek(int pos);
|
||||
void pauseResume();
|
||||
private Q_SLOTS:
|
||||
void setSliderRange(int duration);
|
||||
private:
|
||||
MpvWidget *m_mpv;
|
||||
QSlider *m_slider;
|
||||
QPushButton *m_openBtn;
|
||||
QPushButton *m_playBtn;
|
||||
};
|
||||
|
||||
#endif // MainWindow_H
|
||||
@@ -1,129 +0,0 @@
|
||||
#include "mpvwidget.h"
|
||||
#include <stdexcept>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtCore/QMetaObject>
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
Q_UNUSED(ctx);
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QOpenGLWidget(parent, f)
|
||||
{
|
||||
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");
|
||||
|
||||
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, MpvWidget::on_update, (void *)this);
|
||||
connect(this, SIGNAL(frameSwapped()), SLOT(swapped()));
|
||||
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
}
|
||||
|
||||
MpvWidget::~MpvWidget()
|
||||
{
|
||||
makeCurrent();
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
// 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 MpvWidget::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MpvWidget::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QVariant MpvWidget::getProperty(const QString &name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MpvWidget::initializeGL()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
void MpvWidget::paintGL()
|
||||
{
|
||||
mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height());
|
||||
}
|
||||
|
||||
void MpvWidget::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvWidget::on_mpv_events()
|
||||
{
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE) {
|
||||
break;
|
||||
}
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT positionChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT durationChanged(time);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::on_update(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "update");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef PLAYERWINDOW_H
|
||||
#define PLAYERWINDOW_H
|
||||
|
||||
#include <QtWidgets/QOpenGLWidget>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
|
||||
~MpvWidget();
|
||||
void command(const QVariant& params);
|
||||
void setProperty(const QString& name, const QVariant& value);
|
||||
QVariant getProperty(const QString& name) const;
|
||||
QSize sizeHint() const { return QSize(480, 270);}
|
||||
Q_SIGNALS:
|
||||
void durationChanged(int value);
|
||||
void positionChanged(int value);
|
||||
protected:
|
||||
void initializeGL() Q_DECL_OVERRIDE;
|
||||
void paintGL() Q_DECL_OVERRIDE;
|
||||
private Q_SLOTS:
|
||||
void swapped();
|
||||
void on_mpv_events();
|
||||
private:
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
static void on_update(void *ctx);
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PLAYERWINDOW_H
|
||||
@@ -1,13 +0,0 @@
|
||||
CONFIG -= app_bundle
|
||||
QT += widgets
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
HEADERS = \
|
||||
mpvwidget.h \
|
||||
mainwindow.h
|
||||
SOURCES = main.cpp \
|
||||
mpvwidget.cpp \
|
||||
mainwindow.cpp
|
||||
@@ -1,158 +0,0 @@
|
||||
// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2`
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
|
||||
static Uint32 wakeup_on_mpv_redraw, wakeup_on_mpv_events;
|
||||
|
||||
static void die(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *get_proc_address_mpv(void *fn_ctx, const char *name)
|
||||
{
|
||||
return SDL_GL_GetProcAddress(name);
|
||||
}
|
||||
|
||||
static void on_mpv_events(void *ctx)
|
||||
{
|
||||
SDL_Event event = {.type = wakeup_on_mpv_events};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
static void on_mpv_redraw(void *ctx)
|
||||
{
|
||||
SDL_Event event = {.type = wakeup_on_mpv_redraw};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
die("pass a single media file as argument");
|
||||
|
||||
mpv_handle *mpv = mpv_create();
|
||||
if (!mpv)
|
||||
die("context init failed");
|
||||
|
||||
// Some minor options can only be set before mpv_initialize().
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
die("mpv init failed");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
die("SDL init failed");
|
||||
|
||||
SDL_Window *window =
|
||||
SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
if (!window)
|
||||
die("failed to create SDL window");
|
||||
|
||||
// The OpenGL API is somewhat separate from the normal mpv API. This only
|
||||
// returns NULL if no OpenGL support is compiled.
|
||||
mpv_opengl_cb_context *mpv_gl = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
die("failed to create mpv GL API handle");
|
||||
|
||||
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
|
||||
if (!glcontext)
|
||||
die("failed to create SDL GL context");
|
||||
|
||||
// This makes mpv use the currently set GL context. It will use the callback
|
||||
// to resolve GL builtin functions, as well as extensions.
|
||||
if (mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address_mpv, NULL) < 0)
|
||||
die("failed to initialize mpv GL context");
|
||||
|
||||
// Actually using the opengl_cb state has to be explicitly requested.
|
||||
// Otherwise, mpv will create a separate platform window.
|
||||
if (mpv_set_option_string(mpv, "vo", "opengl-cb") < 0)
|
||||
die("failed to set VO");
|
||||
|
||||
// We use events for thread-safe notification of the SDL main loop.
|
||||
// Generally, the wakeup callbacks (set further below) should do as least
|
||||
// work as possible, and merely wake up another thread to do actual work.
|
||||
// On SDL, waking up the mainloop is the ideal course of action. SDL's
|
||||
// SDL_PushEvent() is thread-safe, so we use that.
|
||||
wakeup_on_mpv_redraw = SDL_RegisterEvents(1);
|
||||
wakeup_on_mpv_events = SDL_RegisterEvents(1);
|
||||
if (wakeup_on_mpv_redraw == (Uint32)-1 || wakeup_on_mpv_events == (Uint32)-1)
|
||||
die("could not register events");
|
||||
|
||||
// When normal mpv events are available.
|
||||
mpv_set_wakeup_callback(mpv, on_mpv_events, NULL);
|
||||
|
||||
// When a new frame should be drawn with mpv_opengl_cb_draw().
|
||||
// (Separate from the normal event handling mechanism for the sake of
|
||||
// users which run OpenGL on a different thread.)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, on_mpv_redraw, NULL);
|
||||
|
||||
// Play this file. Note that this starts playback asynchronously.
|
||||
const char *cmd[] = {"loadfile", argv[1], NULL};
|
||||
mpv_command(mpv, cmd);
|
||||
|
||||
while (1) {
|
||||
SDL_Event event;
|
||||
if (SDL_WaitEvent(&event) != 1)
|
||||
die("event loop error");
|
||||
int redraw = 0;
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
goto done;
|
||||
case SDL_WINDOWEVENT:
|
||||
if (event.window.event == SDL_WINDOWEVENT_EXPOSED)
|
||||
redraw = 1;
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_SPACE)
|
||||
mpv_command_string(mpv, "cycle pause");
|
||||
break;
|
||||
default:
|
||||
// Happens when a new video frame should be rendered, or if the
|
||||
// current frame has to be redrawn e.g. due to OSD changes.
|
||||
if (event.type == wakeup_on_mpv_redraw)
|
||||
redraw = 1;
|
||||
// Happens when at least 1 new event is in the mpv event queue.
|
||||
if (event.type == wakeup_on_mpv_events) {
|
||||
// Handle all remaining mpv events.
|
||||
while (1) {
|
||||
mpv_event *mp_event = mpv_wait_event(mpv, 0);
|
||||
if (mp_event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
printf("event: %s\n", mpv_event_name(mp_event->event_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (redraw) {
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
// Note:
|
||||
// - The 0 is the FBO to use; 0 is the default framebuffer (i.e.
|
||||
// render to the window directly.
|
||||
// - The negative height tells mpv to flip the coordinate system.
|
||||
// - If you do not want the video to cover the whole screen, or want
|
||||
// to apply any form of fancy transformation, you will have to
|
||||
// render to a FBO.
|
||||
// - See opengl_cb.h on what OpenGL environment mpv expects, and
|
||||
// other API details.
|
||||
mpv_opengl_cb_draw(mpv_gl, 0, w, -h);
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
}
|
||||
done:
|
||||
|
||||
// Destroy the GL renderer and all of the GL objects it allocated. If video
|
||||
// is still running, the video track will be deselected.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
|
||||
mpv_terminate_destroy(mpv);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Build with: gcc -o simple simple.c `pkg-config --libs --cflags mpv`
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
static inline void check_error(int status)
|
||||
{
|
||||
if (status < 0) {
|
||||
printf("mpv API error: %s\n", mpv_error_string(status));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("pass a single media file as argument\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mpv_handle *ctx = mpv_create();
|
||||
if (!ctx) {
|
||||
printf("failed creating context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enable default key bindings, so the user can actually interact with
|
||||
// the player (and e.g. close the window).
|
||||
check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes"));
|
||||
mpv_set_option_string(ctx, "input-vo-keyboard", "yes");
|
||||
int val = 1;
|
||||
check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val));
|
||||
|
||||
// Done setting up options.
|
||||
check_error(mpv_initialize(ctx));
|
||||
|
||||
// Play this file.
|
||||
const char *cmd[] = {"loadfile", argv[1], NULL};
|
||||
check_error(mpv_command(ctx, cmd));
|
||||
|
||||
// Let it play, and wait until the user quits.
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(ctx, 10000);
|
||||
printf("event: %s\n", mpv_event_name(event->event_id));
|
||||
if (event->event_id == MPV_EVENT_SHUTDOWN)
|
||||
break;
|
||||
}
|
||||
|
||||
mpv_terminate_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
+38
-13
@@ -104,22 +104,21 @@ Installing MSYS2
|
||||
It doesn't matter whether the i686 or the x86_64 version is used. Both can
|
||||
build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
|
||||
|
||||
2. Add ``C:\msys64\mingw64\bin`` to your ``%PATH%``. mpv will depend on several
|
||||
DLLs in this folder.
|
||||
2. Start a MinGW-w64 shell (``mingw64_shell.bat``). Note that this is different
|
||||
from the MSYS2 shell that is started from the final installation dialog.
|
||||
|
||||
3. Start a MinGW-w64 shell (``mingw64_shell.bat``). For a 32-bit build, use
|
||||
``mingw32_shell.bat``.
|
||||
For a 32-bit build, use ``mingw32_shell.bat``.
|
||||
|
||||
Updating MSYS2
|
||||
--------------
|
||||
|
||||
To prevent errors during post-install, ``msys2-runtime`` and ``pacman`` must be
|
||||
updated first.
|
||||
To prevent errors during post-install, the MSYS2 core runtime must be updated
|
||||
separately.
|
||||
|
||||
```bash
|
||||
# Check for updates to msys2-runtime and pacman. If there were updates, restart
|
||||
# MSYS2 before continuing.
|
||||
pacman -Sy --needed msys2-runtime pacman
|
||||
# Check for core updates. If instructed, close the shell window and reopen it
|
||||
# before continuing.
|
||||
update-core
|
||||
|
||||
# Update everything else
|
||||
pacman -Su
|
||||
@@ -130,14 +129,14 @@ Installing mpv dependencies
|
||||
|
||||
```bash
|
||||
# Install MSYS2 build dependencies and a MinGW-w64 compiler
|
||||
pacman -S git pkg-config python3 mingw-w64-x86_64-gcc
|
||||
pacman -S git mingw-w64-x86_64-pkg-config python mingw-w64-x86_64-gcc
|
||||
|
||||
# Install the most important MinGW-w64 dependencies. libass, libbluray and
|
||||
# lcms2 are also pulled in as dependencies of ffmpeg.
|
||||
pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
|
||||
|
||||
# Install additional (optional) dependencies
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git
|
||||
```
|
||||
|
||||
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
|
||||
@@ -157,14 +156,14 @@ Finally, compile and install mpv. Binaries will be installed to
|
||||
|
||||
```bash
|
||||
# For a 32-bit build, use --prefix=/mingw32 instead
|
||||
./waf configure CC=gcc --prefix=/mingw64
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Or, compile and install both libmpv and mpv:
|
||||
|
||||
```bash
|
||||
./waf configure CC=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
|
||||
# waf installs libmpv to the wrong directory, so fix it up
|
||||
@@ -173,3 +172,29 @@ mv -f /mingw64/bin/libmpv.dll.a /mingw64/lib/
|
||||
sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
```
|
||||
|
||||
Running mpv
|
||||
-----------
|
||||
|
||||
If you want to run mpv from the MinGW-w64 shell, you will find the experience
|
||||
much more pleasant if you use the ``winpty`` utility
|
||||
|
||||
```bash
|
||||
pacman -S winpty
|
||||
winpty mpv.com ToS-4k-1920.mov
|
||||
```
|
||||
|
||||
If you want to move / copy ``mpv.exe`` and ``mpv.com`` to somewhere other than
|
||||
``/mingw64/bin/`` for use outside the MinGW-w64 shell, they will still depend on
|
||||
DLLs in that folder. The simplest solution is to add ``C:\msys64\mingw64\bin``
|
||||
to the windows system ``%PATH%``. Beware though that this can cause problems or
|
||||
confusion in Cygwin if that is also installed on the machine.
|
||||
|
||||
Use of the ANGLE OpenGL backend requires a copy of ``d3dcompiler_43.dll`` (yes,
|
||||
exactly 43) in the path or in the same folder as mpv. It must be of the same
|
||||
architecture (x86_64 / i686) as the mpv you compiled. You can find a copy in the
|
||||
official mpv builds:
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-x86_64-20160118.7z
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-i686-20160118.7z
|
||||
@@ -19,6 +19,17 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.0 ---
|
||||
- deprecate "track-list/N/audio-channels" property (use
|
||||
"track-list/N/demux-channel-count" instead)
|
||||
- remove write access to "stream-pos", and change semantics for read access
|
||||
- Lua scripts now don't suspend mpv by default while script code is run
|
||||
- add "cache-speed" property
|
||||
- rename --input-unix-socket to --input-ipc-server, and make it work on
|
||||
Windows too
|
||||
- change the exact behavior of the "video-zoom" property
|
||||
- --video-unscaled no longer disables --video-zoom and --video-aspect
|
||||
To force the old behavior, set --video-zoom=0 and --video-aspect=0
|
||||
--- mpv 0.16.0 ---
|
||||
- change --audio-channels default to stereo (use --audio-channels=auto to
|
||||
get the old default)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Client API (libmpv) documentation and examples
|
||||
==============================================
|
||||
|
||||
libmpv lets you embed mpv as a library. It is meant to enable development of
|
||||
GUI frontends.
|
||||
|
||||
Since libmpv merely allows access to underlying mechanisms that can control
|
||||
mpv, documentation is spread over a few places:
|
||||
|
||||
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
|
||||
- http://mpv.io/manual/master/#options
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
+88
-9
@@ -921,8 +921,9 @@ Property list
|
||||
useless. It looks like this can be different from ``path`` only when
|
||||
using e.g. ordered chapters.)
|
||||
|
||||
``stream-pos`` (RW)
|
||||
Raw byte position in source stream.
|
||||
``stream-pos``
|
||||
Raw byte position in source stream. Technically, this returns the position
|
||||
of the most recent packet passed to a decoder.
|
||||
|
||||
``stream-end``
|
||||
Raw end position in bytes in source stream.
|
||||
@@ -1194,6 +1195,11 @@ Property list
|
||||
``cache-used`` (R)
|
||||
Total used cache size in KB.
|
||||
|
||||
``cache-speed`` (R)
|
||||
Current I/O read speed between the cache and the lower layer (like network).
|
||||
This gives the number bytes per seconds over a 1 second window (using
|
||||
the type ``MPV_FORMAT_INT64`` for the client API).
|
||||
|
||||
``cache-idle`` (R)
|
||||
Returns ``yes`` if the cache is idle, which means the cache is filled as
|
||||
much as possible, and is currently not reading more data.
|
||||
@@ -1237,10 +1243,10 @@ Property list
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is active, changing
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
property until the mixer becomes active either. (The options, if set, will
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
@@ -1707,10 +1713,6 @@ Property list
|
||||
``track-list/N/lang``
|
||||
Track language as identified by the file. Not always available.
|
||||
|
||||
``track-list/N/audio-channels``
|
||||
For audio tracks, the number of audio channels in the audio stream.
|
||||
Not always accurate (depends on container hints). Not always available.
|
||||
|
||||
``track-list/N/albumart``
|
||||
``yes`` if this is a video track that consists of a single picture,
|
||||
``no`` or unavailable otherwise. This is used for video tracks that are
|
||||
@@ -1746,6 +1748,29 @@ Property list
|
||||
match even if the default (builtin) demuxer is used, but there is
|
||||
no hard guarantee.
|
||||
|
||||
``track-list/N/decoder-desc``
|
||||
If this track is being decoded, the human-readable decoder name,
|
||||
|
||||
``track-list/N/demux-w``, ``track-list/N/demux-h``
|
||||
Video size hint as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-channel-count``
|
||||
Number of audio channels as indicated by the container. (Not always
|
||||
accurate - in particular, the track could be decoded as a different
|
||||
number of channels.)
|
||||
|
||||
``track-list/N/demux-channels``
|
||||
Channel layout as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-samplerate``
|
||||
Audio sample rate as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-fps``
|
||||
Video FPS as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/audio-channels`` (deprecated)
|
||||
Deprecated alias for ``track-list/N/demux-channel-count``.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -1759,13 +1784,20 @@ Property list
|
||||
"src-id" MPV_FORMAT_INT64
|
||||
"title" MPV_FORMAT_STRING
|
||||
"lang" MPV_FORMAT_STRING
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
"albumart" MPV_FORMAT_FLAG
|
||||
"default" MPV_FORMAT_FLAG
|
||||
"forced" MPV_FORMAT_FLAG
|
||||
"external" MPV_FORMAT_FLAG
|
||||
"external-filename" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"decoder-desc" MPV_FORMAT_STRING
|
||||
"demux-w" MPV_FORMAT_INT64
|
||||
"demux-h" MPV_FORMAT_INT64
|
||||
"demux-channel-count" MPV_FORMAT_INT64
|
||||
"demux-channels" MPV_FORMAT_STRING
|
||||
"demux-samplerate" MPV_FORMAT_INT64
|
||||
"demux-fps" MPV_FORMAT_DOUBLE
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
|
||||
``chapter-list``
|
||||
List of chapters, current entry marked. Currently, the raw property value
|
||||
@@ -1819,6 +1851,9 @@ Property list
|
||||
``video-rotate`` (RW)
|
||||
See ``--video-rotate`` option.
|
||||
|
||||
``video-stereo-mode`` (RW)
|
||||
See ``--video-stereo-mode`` option.
|
||||
|
||||
``seekable``
|
||||
Return whether it's generally possible to seek in the current file.
|
||||
|
||||
@@ -1962,6 +1997,43 @@ Property list
|
||||
In some cases, the protocol will not actually be supported (consider
|
||||
``https`` if ffmpeg is not compiled with TLS support).
|
||||
|
||||
``decoder-list``
|
||||
List of decoders supported. This lists decoders which can be passed to
|
||||
``--vd`` and ``--ad``.
|
||||
|
||||
``family``
|
||||
Decoder driver. Usually ``lavc`` for libavcodec.
|
||||
|
||||
``codec``
|
||||
Canonical codec name, which identifies the format the decoder can
|
||||
handle.
|
||||
|
||||
``driver``
|
||||
The name of the decoder itself. Often, this is the same as ``codec``.
|
||||
Sometimes it can be different. It is used to distinguish multiple
|
||||
decoders for the same codec.
|
||||
|
||||
``description``
|
||||
Human readable description of the decoder and codec.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_ARRAY
|
||||
MPV_FORMAT_NODE_MAP (for each decoder entry)
|
||||
"family" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"driver" MPV_FORMAT_STRING
|
||||
"description" MPV_FORMAT_STRING
|
||||
|
||||
``encoder-list``
|
||||
List of libavcodec encoders. This has the same format as ``decoder-list``.
|
||||
The encoder names (``driver`` entries) can be passed to ``--ovc`` and
|
||||
``--oac`` (without the ``lavc:`` prefix required by ``--vd`` and ``--ad``).
|
||||
|
||||
``mpv-version``
|
||||
Return the mpv version/copyright string. Depending on how the binary was
|
||||
built, it might contain either a release version, or just a git hash.
|
||||
@@ -1970,6 +2042,13 @@ Property list
|
||||
Return the configuration arguments which were passed to the build system
|
||||
(typically the way ``./waf configure ...`` was invoked).
|
||||
|
||||
``ffmpeg-version``
|
||||
Return the contents of the ``av_version_info()`` API call. This is a string
|
||||
which identifies the build in some way, either through a release version
|
||||
number, or a git hash. This applies to Libav as well (the property is
|
||||
still named the same.) This property is unavailable if mpv is linked against
|
||||
older FFmpeg and Libav versions.
|
||||
|
||||
``options/<name>`` (RW)
|
||||
Read-only access to value of option ``--<name>``. Most options can be
|
||||
changed at runtime by writing to this property. Note that many options
|
||||
|
||||
+32
-7
@@ -1,10 +1,10 @@
|
||||
JSON IPC
|
||||
========
|
||||
|
||||
mpv can be controlled by external programs using the JSON-based IPC protocol. It
|
||||
can be enabled by specifying the path to a unix socket using the option
|
||||
``--input-unix-socket``. Clients can connect to this socket and send commands to
|
||||
the player or receive events from it.
|
||||
mpv can be controlled by external programs using the JSON-based IPC protocol.
|
||||
It can be enabled by specifying the path to a unix socket or a named pipe using
|
||||
the option ``--input-ipc-server``. Clients can connect to this socket and send
|
||||
commands to the player or receive events from it.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -17,12 +17,12 @@ the player or receive events from it.
|
||||
Socat example
|
||||
-------------
|
||||
|
||||
You can use the ``socat`` tool to send commands (and receive reply) from the
|
||||
You can use the ``socat`` tool to send commands (and receive replies) from the
|
||||
shell. Assuming mpv was started with:
|
||||
|
||||
::
|
||||
|
||||
mpv file.mkv --input-unix-socket=/tmp/mpvsocket
|
||||
mpv file.mkv --input-ipc-server=/tmp/mpvsocket
|
||||
|
||||
Then you can control it using socat:
|
||||
|
||||
@@ -46,6 +46,31 @@ It's also possible to send input.conf style text-only commands:
|
||||
But you won't get a reply over the socket. (This particular command shows the
|
||||
playback time on the player's OSD.)
|
||||
|
||||
Command Prompt example
|
||||
----------------------
|
||||
|
||||
Unfortunately, it's not as easy to test the IPC protocol on Windows, since
|
||||
Windows ports of socat (in Cygwin and MSYS2) don't understand named pipes. In
|
||||
the absence of a simple tool to send and receive from bidirectional pipes, the
|
||||
``echo`` command can be used to send commands, but not receive replies from the
|
||||
command prompt.
|
||||
|
||||
Assuming mpv was started with:
|
||||
|
||||
::
|
||||
|
||||
mpv file.mkv --input-ipc-server=\\.\pipe\mpvsocket
|
||||
|
||||
You can send commands from a command prompt:
|
||||
|
||||
::
|
||||
|
||||
echo show_text ${playback-time} >\\.\pipe\mpvsocket
|
||||
|
||||
To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
some wrapper like .NET's NamedPipeClientStream.)
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
@@ -102,7 +127,7 @@ break character (``\n``).
|
||||
|
||||
If the first character (after skipping whitespace) is not ``{``, the command
|
||||
will be interpreted as non-JSON text command, as they are used in input.conf
|
||||
(or ``mpv_command_string()`` in the client API). Additionally, line starting
|
||||
(or ``mpv_command_string()`` in the client API). Additionally, lines starting
|
||||
with ``#`` and empty lines are ignored.
|
||||
|
||||
Currently, embedded 0 bytes terminate the current line, but you should not
|
||||
|
||||
+4
-9
@@ -192,7 +192,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Register callback to be run on a key binding. The binding will be mapped to
|
||||
the given ``key``, which is a string describing the physical key. This uses
|
||||
the same key names as in input.conf, and also allows combinations
|
||||
(e.g. ``ctrl+a``).
|
||||
(e.g. ``ctrl+a``). If the key is empty or ``nil``, no physical key is
|
||||
registered, but the user still can create own bindings (see below).
|
||||
|
||||
After calling this function, key presses will cause the function ``fn`` to
|
||||
be called (unless the user remapped the key with another binding).
|
||||
@@ -255,7 +256,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
::
|
||||
|
||||
y script-binding fooscript.something
|
||||
y script-binding fooscript/something
|
||||
|
||||
``mp.add_forced_key_binding(...)``
|
||||
This works almost the same as ``mp.add_key_binding``, but registers the
|
||||
@@ -412,7 +413,7 @@ are useful only in special situations.
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
This is automatically called by the event handler.
|
||||
Before mpv 0.17.0, this was automatically called by the event handler.
|
||||
|
||||
``mp.resume()``
|
||||
Undo one ``mp.suspend()`` call. ``mp.suspend()`` increments an internal
|
||||
@@ -423,10 +424,6 @@ are useful only in special situations.
|
||||
This resets the internal suspend counter and resumes the player. (It's
|
||||
like calling ``mp.resume()`` until the player is actually resumed.)
|
||||
|
||||
You might want to call this if you're about to do something that takes a
|
||||
long time, but doesn't really need access to the player (like a network
|
||||
operation). Note that you still can access the player at any time.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
pipe. (See ``client.h`` for details.)
|
||||
@@ -449,8 +446,6 @@ are useful only in special situations.
|
||||
``mp.get_wakeup_pipe()`` if you're interested in properly working
|
||||
notification of new events and working timers.
|
||||
|
||||
This function calls ``mp.suspend()`` and ``mp.resume_all()`` on its own.
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
Set the minimum log level of which mpv message output to receive. These
|
||||
messages are normally printed to the terminal. By calling this function,
|
||||
|
||||
+5
-7
@@ -54,7 +54,7 @@ UP and DOWN
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Seek to the previous/next subtitle. Subject to some restrictions and
|
||||
might not always work; see ``sub_seek`` command.
|
||||
might not always work; see ``sub-seek`` command.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
@@ -708,7 +708,9 @@ PROTOCOLS
|
||||
Stitch together parts of multiple files and play them.
|
||||
|
||||
``null://``
|
||||
Simulate an empty file.
|
||||
Simulate an empty file. If opened for writing, it will discard all data.
|
||||
The ``null`` demuxer will specifically pass autoprobing if this protocol
|
||||
is used (while it's not automatically invoked for empty files).
|
||||
|
||||
``memory://data``
|
||||
Use the ``data`` part as source data.
|
||||
@@ -795,9 +797,6 @@ behavior of mpv.
|
||||
If set, XDG-style system configuration directories are used. Otherwise,
|
||||
the UNIX convention (``PREFIX/etc/mpv/``) is used.
|
||||
|
||||
``TERM``
|
||||
Used to determine terminal type.
|
||||
|
||||
``MPV_HOME``
|
||||
Directory where mpv looks for user settings. Overrides ``HOME``, and mpv
|
||||
will try to load the config file as ``$MPV_HOME/mpv.conf``.
|
||||
@@ -808,8 +807,7 @@ behavior of mpv.
|
||||
of ``--v`` options passed to the command line.
|
||||
|
||||
``MPV_LEAK_REPORT``
|
||||
If set to ``1``, enable internal talloc leak reporting. Note that this can
|
||||
cause trouble with multithreading, so only developers should use this.
|
||||
If set to ``1``, enable internal talloc leak reporting.
|
||||
|
||||
``LADSPA_PATH``
|
||||
Specifies the search path for LADSPA plugins. If it is unset, fully
|
||||
|
||||
+52
-24
@@ -580,7 +580,10 @@ Video
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:dxva2: requires ``--vo=opengl:backend=angle`` or
|
||||
``--vo=opengl:backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va-copy: experimental (Windows only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
@@ -596,9 +599,14 @@ Video
|
||||
forcing it with ``--vo=opengl:backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's likely less efficient
|
||||
than the ``vaapi`` mode.
|
||||
Most video filters will not work with hardware decoding as they are
|
||||
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
|
||||
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
|
||||
|
||||
The ``vaapi-copy`` and ``dxva2-copy`` modes allow you to use hardware
|
||||
decoding with any VO, backend or filter. Because these copy the decoded
|
||||
video back to system RAM, they're likely less efficient than the ``vaapi``
|
||||
or ``dxva2`` modes respectively.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -676,15 +684,12 @@ Video
|
||||
``--video-unscaled``
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options. (But not all; for
|
||||
example ``--video-zoom`` does nothing if this option is enabled.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
would require scaling the video in the X or Y direction, but this option
|
||||
disables scaling, disabling all aspect correction.
|
||||
can be influenced by the other ``--video-...`` options.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
scaled. For example, this can influence chroma conversion.
|
||||
scaled. For example, this can influence chroma conversion. The video will
|
||||
also still be scaled in one dimension if the source uses non-square pixels
|
||||
(e.g. anamorphic widescreen DVDs).
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
@@ -724,12 +729,10 @@ Video
|
||||
appear in files, but can't be handled properly by mpv.
|
||||
|
||||
``--video-zoom=<value>``
|
||||
Adjust the video display scale factor by the given value. The unit is in
|
||||
fractions of the (scaled) window video size.
|
||||
|
||||
For example, given a 1280x720 video shown in a 1280x720 window,
|
||||
``--video-zoom=-0.1`` would make the video by 128 pixels smaller in
|
||||
X direction, and 72 pixels in Y direction.
|
||||
Adjust the video display scale factor by the given value. The parameter is
|
||||
given log 2. For example, ``--video-zoom=0`` is unscaled,
|
||||
``--video-zoom=1`` is twice the size, ``--video-zoom=-2`` is one fourth of
|
||||
the size, and so on.
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
@@ -2066,8 +2069,8 @@ Window
|
||||
This option might be removed in the future.
|
||||
|
||||
``--x11-bypass-compositor=<yes|no>``
|
||||
If set to ``yes`` (default), then ask the compositor to unredirect the
|
||||
mpv window. This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
If set to ``yes``, then ask the compositor to unredirect the mpv window
|
||||
(default: no). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
|
||||
|
||||
Disc Devices
|
||||
@@ -2433,7 +2436,7 @@ Input
|
||||
``--input-file=<filename>``
|
||||
Read commands from the given file. Mostly useful with a FIFO. Since
|
||||
mpv 0.7.0 also understands JSON commands (see `JSON IPC`_), but you can't
|
||||
get replies or events. Use ``--input-unix-socket`` for something
|
||||
get replies or events. Use ``--input-ipc-server`` for something
|
||||
bi-directional. On MS Windows, JSON commands are not available.
|
||||
|
||||
This can also specify a direct file descriptor with ``fd://N`` (UNIX only).
|
||||
@@ -2453,12 +2456,18 @@ Input
|
||||
or intend to read from stdin later on via the loadfile or loadlist slave
|
||||
commands.
|
||||
|
||||
``--input-unix-socket=<filename>``
|
||||
``--input-ipc-server=<filename>``
|
||||
Enable the IPC support and create the listening socket at the given path.
|
||||
|
||||
See `JSON IPC`_ for details.
|
||||
On Linux and Unix, the given path is a regular filesystem path. On Windows,
|
||||
named pipes are used, so the path refers to the pipe namespace
|
||||
(``\\.\pipe\<name>``). If the ``\\.\pipe\`` prefix is missing, mpv will add
|
||||
it automatically before creating the pipe, so
|
||||
``--input-ipc-server=/tmp/mpv-socket`` and
|
||||
``--input-ipc-server=\\.\pipe\tmp\mpv-socket`` are equivalent for IPC on
|
||||
Windows.
|
||||
|
||||
Not available on MS Windows.
|
||||
See `JSON IPC`_ for details.
|
||||
|
||||
``--input-appleremote=<yes|no>``
|
||||
(OS X only)
|
||||
@@ -2613,6 +2622,9 @@ OSD
|
||||
``--osd-bold=<yes|no>``, ``--sub-text-bold=<yes|no>``
|
||||
Format text on bold.
|
||||
|
||||
``--osd-italic=<yes|no>``, ``--sub-text-italic=<yes|no>``
|
||||
Format text on italic.
|
||||
|
||||
``--osd-border-color=<color>``, ``--sub-text-border-color=<color>``
|
||||
See ``--osd-color``. Color used for the OSD/sub font border.
|
||||
|
||||
@@ -2953,6 +2965,22 @@ Terminal
|
||||
:debug: debug messages
|
||||
:trace: very noisy debug messages
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
::
|
||||
|
||||
mpv --msg-level=ao/sndio=no
|
||||
|
||||
Completely silences the output of ao_sndio, which uses the log
|
||||
prefix ``[ao/sndio]``.
|
||||
|
||||
::
|
||||
|
||||
mpv --msg-level=all=warn,ao/alsa=error
|
||||
|
||||
Only show warnings or worse, and let the ao_alsa output show errors
|
||||
only.
|
||||
|
||||
``--term-osd, --no-term-osd``, ``--term-osd=force``
|
||||
Display OSD messages on the console when no video output is available.
|
||||
Enabled by default.
|
||||
@@ -3603,7 +3631,7 @@ Miscellaneous
|
||||
- ``--lavfi-complex='[aid1] asplit [t1] [ao] ; [t1] showvolume [t2] ; [vid1] [t2] overlay [vo]'``
|
||||
Play audio track 1, and overlay the measured volume for each speaker
|
||||
over video track 1.
|
||||
- ``null:// --lavfi-complex='life [vo]'``
|
||||
Conways' Life Game.
|
||||
|
||||
See the FFmpeg libavfilter documentation for details on the filter.
|
||||
|
||||
|
||||
+8
-11
@@ -582,12 +582,10 @@ Available video output drivers are:
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``prescale=<filter>``
|
||||
This option provides non-convolution-based filters for upscaling. These
|
||||
filters resize the video to multiple of the original size (all currently
|
||||
supported prescalers can only perform image doubling in a single pass).
|
||||
Generally another convolution based filter (the main scaler) will be
|
||||
applied after prescaler to match the target display size.
|
||||
``prescale-luma=<filter>``
|
||||
Apply additional pre-scaling (image doubling) on the luma plane
|
||||
(if present). As the name implies, these will run before the main
|
||||
upscaling pass.
|
||||
|
||||
``none``
|
||||
Disable all prescalers. This is the default.
|
||||
@@ -605,10 +603,6 @@ Available video output drivers are:
|
||||
Extremely slow and requires a recent mid or high end graphics card
|
||||
to work smoothly (as of 2015).
|
||||
|
||||
Note that all the filters above are designed (or implemented) to process
|
||||
luma plane only and probably won't work as intended for video in RGB
|
||||
format.
|
||||
|
||||
``prescale-passes=<1..5>``
|
||||
The number of passes to apply the prescaler (defaults to be 1). Setting
|
||||
it to 2 will perform a 4x upscaling.
|
||||
@@ -817,7 +811,8 @@ Available video output drivers are:
|
||||
profiles, and the ``nnedi3`` prescaler.
|
||||
dxinterop (experimental)
|
||||
Win32, using WGL for rendering and Direct3D 9Ex for presentation.
|
||||
Works on Nvidia and AMD only.
|
||||
Works on Nvidia and AMD. Newer Intel chips with the latest drivers
|
||||
may also work.
|
||||
x11
|
||||
X11/GLX
|
||||
wayland
|
||||
@@ -892,6 +887,8 @@ Available video output drivers are:
|
||||
ProPhoto RGB (ROMM)
|
||||
cie1931
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
|
||||
@@ -123,20 +123,28 @@ of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
|
||||
|
||||
## Preferred FFmpeg version
|
||||
|
||||
|
||||
Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended.
|
||||
Older versions are unsupported, even if the build system still happens to
|
||||
accept them. The main reason mpv still builds with older FFmpeg versions is to
|
||||
evade arguing with people (users, distros) who insist on using older FFmpeg
|
||||
versions for no rational reason.
|
||||
|
||||
If you want to use a stable FFmpeg release, use the latest release, which has
|
||||
most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
a 0.X.0 version number. No further maintenance is done.
|
||||
|
||||
Every few months, a new release is cut off of the master branch and is assigned
|
||||
a 0.X.0 version number.
|
||||
|
||||
As part of the maintenance process, minor releases are made, which are assigned
|
||||
0.X.Y version numbers. Minor releases contain bug fixes only. They never merge
|
||||
the master branch, and no features are added to it. Only the latest release is
|
||||
maintained.
|
||||
|
||||
The goal of releases is to provide stability and an unchanged base for the sake
|
||||
of Linux distributions. If you want the newest features, just use the master
|
||||
branch, which is stable most of the time, except sometimes, when it's not.
|
||||
The goal of releases is to make Linux distributions happy. Linux distributions
|
||||
are also expected to apply their own patches in case of bugs and security
|
||||
issues.
|
||||
|
||||
Releases other than the latest release are unsupported and unmaintained.
|
||||
|
||||
|
||||
+7
-1
@@ -11,4 +11,10 @@ to mpv's command line.
|
||||
Where appropriate, they may also be placed in ~/.config/mpv/scripts/ from
|
||||
where they will be automatically loaded when mpv starts.
|
||||
|
||||
Some of these are just for testing mpv internals.
|
||||
This is only a small selection of internally maintained scripts. Some of them
|
||||
are just for testing mpv internals, or serve as examples. An extensive
|
||||
user-edited list of 3rd party scripts is available here:
|
||||
|
||||
https://github.com/mpv-player/mpv/wiki/User-Scripts
|
||||
|
||||
(Anyone can add their own scripts to that list.)
|
||||
@@ -1,74 +0,0 @@
|
||||
-- zones.lua: mpv script for handling commands depending on where the mouse pointer is at,
|
||||
-- mostly for mouse wheel handling, by configuring it via input.conf, e.g.:
|
||||
--
|
||||
-- Ported from avih's ( https://github.com/avih ) zones.js
|
||||
--
|
||||
-- Vertical positions can be top, middle, bottom or "*" to represent the whole column.
|
||||
-- Horizontal positions can be left, middle, bottom or "*" to represent the whole row.
|
||||
-- "default" will be the fallback command to be used if no command is assigned to that area.
|
||||
--
|
||||
-- input.conf example of use:
|
||||
-- # wheel up/down with mouse
|
||||
-- MOUSE_BTN3 script_message_to zones commands "middle-right: add brightness 1" "*-left: add volume 5" "default: seek 10"
|
||||
-- MOUSE_BTN4 script_message_to zones commands "middle-right: add brightness -1" "*-left: add volume -5" "default: seek -10"
|
||||
|
||||
local ZONE_THRESH_PERCENTAGE = 20;
|
||||
-- sides get 20% each, mid gets 60%, same vertically
|
||||
|
||||
local msg = mp.msg
|
||||
|
||||
function getMouseZone()
|
||||
-- returns the mouse zone as two strings [top/middle/bottom], [left/middle/right], e.g. "middle", "right"
|
||||
|
||||
local screenW, screenH = mp.get_osd_resolution()
|
||||
local mouseX, mouseY = mp.get_mouse_pos()
|
||||
|
||||
local threshY = screenH * ZONE_THRESH_PERCENTAGE / 100
|
||||
local threshX = screenW * ZONE_THRESH_PERCENTAGE / 100
|
||||
|
||||
local yZone = (mouseY < threshY) and "top" or (mouseY < (screenH - threshY)) and "middle" or "bottom"
|
||||
local xZone = (mouseX < threshX) and "left" or (mouseX < (screenW - threshX)) and "middle" or "right"
|
||||
|
||||
return yZone, xZone
|
||||
end
|
||||
|
||||
function main (...)
|
||||
local arg={...}
|
||||
msg.debug('commands: \n\t'..table.concat(arg,'\n\t'))
|
||||
|
||||
local keyY, keyX = getMouseZone()
|
||||
msg.debug("mouse at: " .. keyY .. '-' .. keyX)
|
||||
|
||||
local fallback = nil
|
||||
|
||||
for i, v in ipairs(arg) do
|
||||
cmdY = v:match("^([%w%*]+)%-?[%w%*]*:")
|
||||
cmdX = v:match("^[%w%*]*%-([%w%*]+)%s*:")
|
||||
cmd = v:match("^[%S]-%s*:%s*(.+)")
|
||||
msg.debug('cmdY: '..tostring(cmdY))
|
||||
msg.debug('cmdX: '..tostring(cmdX))
|
||||
msg.debug('cmd : '..tostring(cmd))
|
||||
|
||||
if (cmdY == keyY and cmdX == keyX) then
|
||||
msg.verbose("running cmd: "..cmd)
|
||||
mp.command(cmd)
|
||||
return
|
||||
elseif (cmdY == "*" and cmdX == keyX) or
|
||||
(cmdY == keyY and cmdX == "*") then
|
||||
msg.verbose("running cmd: "..cmd)
|
||||
mp.command(cmd)
|
||||
return
|
||||
elseif cmdY == "default" then
|
||||
fallback = cmd
|
||||
end
|
||||
end
|
||||
if fallback ~= nil then
|
||||
msg.verbose("running cmd: "..fallback)
|
||||
mp.command(fallback)
|
||||
return
|
||||
else
|
||||
msg.debug("no command assigned for "..keyY .. '-' .. keyX)
|
||||
return
|
||||
end
|
||||
end
|
||||
mp.register_script_message("commands", main)
|
||||
+8
-2
@@ -69,11 +69,17 @@ chomp $profile_comp;
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# mpv zsh completion
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
@@ -120,7 +126,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
|
||||
+20
-4
@@ -45,6 +45,7 @@ struct priv {
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
@@ -84,6 +85,8 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->codec_timebase = (AVRational){0};
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
@@ -128,8 +131,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
// demux_mkv
|
||||
mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
|
||||
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, c->lav_headers);
|
||||
mp_set_lav_codec_headers(lavc_context, c);
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
@@ -185,10 +187,23 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
if (ret >= 0 && mpkt)
|
||||
mpkt->len = 0;
|
||||
ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
if (ret >= 0)
|
||||
got_frame = 1;
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
#else
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
if (mpkt) {
|
||||
// At least "shorten" decodes sub-frames, instead of the whole packet.
|
||||
@@ -205,6 +220,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return -1;
|
||||
@@ -212,7 +228,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (!got_frame)
|
||||
return 0;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
|
||||
@@ -171,7 +171,11 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
stream->codecpar->codec_id = spdif_ctx->codec_id;
|
||||
#else
|
||||
stream->codec->codec_id = spdif_ctx->codec_id;
|
||||
#endif
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
|
||||
@@ -245,6 +245,37 @@ void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]
|
||||
out_formats[num] = 0;
|
||||
}
|
||||
|
||||
// Return the best match to src_samplerate from the list provided in the array
|
||||
// *available, which must be terminated by 0, or itself NULL. If *available is
|
||||
// empty or NULL, return a negative value. Exact match to src_samplerate is
|
||||
// most preferred, followed by the lowest integer multiple, followed by the
|
||||
// maximum of *available.
|
||||
int af_select_best_samplerate(int src_samplerate, const int *available)
|
||||
{
|
||||
if (!available)
|
||||
return -1;
|
||||
|
||||
int min_mult_rate = INT_MAX;
|
||||
int max_rate = INT_MIN;
|
||||
for (int i = 0; available[i]; i++) {
|
||||
if (available[i] == src_samplerate)
|
||||
return available[i];
|
||||
|
||||
if (!(available[i] % src_samplerate))
|
||||
min_mult_rate = MPMIN(min_mult_rate, available[i]);
|
||||
|
||||
max_rate = MPMAX(max_rate, available[i]);
|
||||
}
|
||||
|
||||
if (min_mult_rate < INT_MAX)
|
||||
return min_mult_rate;
|
||||
|
||||
if (max_rate > INT_MIN)
|
||||
return max_rate;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return the number of samples that make up one frame in this format.
|
||||
// You get the byte size by multiplying them with sample size and channel count.
|
||||
int af_format_sample_alignment(int format)
|
||||
|
||||
@@ -76,6 +76,7 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
int af_select_best_samplerate(int src_sampelrate, const int *available);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ static void uninit(struct ao *ao)
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
MP_WARN(ao, "can't revert to original device format\n");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
@@ -108,6 +108,11 @@ static int init(struct ao *ao)
|
||||
|
||||
codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
if (samplerate > 0)
|
||||
ao->samplerate = samplerate;
|
||||
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
|
||||
@@ -364,17 +364,9 @@ static bool search_samplerates(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; supported[i]; i++) {
|
||||
// first choose the lowest integer multiple of the sample rate
|
||||
if (!(supported[i] % ao->samplerate)) {
|
||||
change_waveformat_samplerate(wformat, supported[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// then choose the highest supported (if any)
|
||||
if (n) {
|
||||
change_waveformat_samplerate(wformat, supported[n-1]);
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate, supported);
|
||||
if (samplerate > 0) {
|
||||
change_waveformat_samplerate(wformat, samplerate);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+37
-1
@@ -25,9 +25,12 @@
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
|
||||
@@ -68,6 +71,19 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
avctx->has_b_frames = st->has_b_frames;
|
||||
}
|
||||
|
||||
// This only copies ffmpeg-native codec parameters. Parameters produced by
|
||||
// other demuxers must be handled manually.
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
{
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (c->lav_codecpar)
|
||||
avcodec_parameters_to_context(avctx, c->lav_codecpar);
|
||||
#else
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(avctx, c->lav_headers);
|
||||
#endif
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
|
||||
@@ -144,6 +160,11 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
|
||||
avctx->thread_count = threads;
|
||||
}
|
||||
|
||||
static bool is_crap(AVCodec *codec)
|
||||
{
|
||||
return !!strstr(codec->name, "_vdpau");
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
@@ -151,7 +172,22 @@ void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_decoder(cur) && cur->type == type) {
|
||||
if (av_codec_is_decoder(cur) && cur->type == type && !is_crap(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (Abuses the decoder list data structures.)
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
for (;;) {
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_encoder(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
|
||||
@@ -26,16 +26,19 @@
|
||||
|
||||
struct mp_decoder_list;
|
||||
struct demux_packet;
|
||||
struct mp_codec_params;
|
||||
struct AVDictionary;
|
||||
struct mp_log;
|
||||
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads);
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list);
|
||||
int mp_codec_to_av_codec_id(const char *codec);
|
||||
const char *mp_codec_from_av_codec_id(int codec_id);
|
||||
void mp_set_avdict(struct AVDictionary **dict, char **kv);
|
||||
|
||||
+7
-2
@@ -131,12 +131,17 @@ static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt,
|
||||
struct mp_log *log = get_av_log(ptr);
|
||||
|
||||
if (mp_msg_test(log, mp_level)) {
|
||||
char buffer[4096] = "";
|
||||
int pos = 0;
|
||||
const char *prefix = avc ? avc->item_name(ptr) : NULL;
|
||||
if (log_print_prefix && prefix)
|
||||
mp_msg(log, mp_level, "%s: ", prefix);
|
||||
pos = snprintf(buffer, sizeof(buffer), "%s: ", prefix);
|
||||
log_print_prefix = fmt[strlen(fmt) - 1] == '\n';
|
||||
|
||||
mp_msg_va(log, mp_level, fmt, vl);
|
||||
pos = MPMIN(MPMAX(pos, 0), sizeof(buffer));
|
||||
vsnprintf(buffer + pos, sizeof(buffer) - pos, fmt, vl);
|
||||
|
||||
mp_msg(log, mp_level, "%s", buffer);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
|
||||
+33
-27
@@ -31,6 +31,7 @@
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "misc/ring.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "options/options.h"
|
||||
#include "osdep/terminal.h"
|
||||
#include "osdep/io.h"
|
||||
@@ -41,9 +42,6 @@
|
||||
#include "msg.h"
|
||||
#include "msg_control.h"
|
||||
|
||||
/* maximum message length of mp_msg */
|
||||
#define MSGSIZE_MAX 6144
|
||||
|
||||
struct mp_log_root {
|
||||
struct mpv_global *global;
|
||||
// --- protected by mp_msg_lock
|
||||
@@ -67,7 +65,7 @@ struct mp_log_root {
|
||||
* synchronized mp_log tree.) */
|
||||
atomic_ulong reload_counter;
|
||||
// --- protected by mp_msg_lock
|
||||
char buffer[MSGSIZE_MAX];
|
||||
bstr buffer;
|
||||
};
|
||||
|
||||
struct mp_log {
|
||||
@@ -77,6 +75,7 @@ struct mp_log {
|
||||
int level; // minimum log level for any outputs
|
||||
int terminal_level; // minimum log level for terminal output
|
||||
atomic_ulong reload_counter;
|
||||
char *partial;
|
||||
};
|
||||
|
||||
struct mp_log_buffer {
|
||||
@@ -239,7 +238,8 @@ static bool test_terminal_level(struct mp_log *log, int lev)
|
||||
!(lev == MSGL_STATUS && terminal_in_background());
|
||||
}
|
||||
|
||||
static void print_terminal_line(struct mp_log *log, int lev, char *text)
|
||||
static void print_terminal_line(struct mp_log *log, int lev,
|
||||
char *text, char *trail)
|
||||
{
|
||||
if (!test_terminal_level(log, lev))
|
||||
return;
|
||||
@@ -268,7 +268,7 @@ static void print_terminal_line(struct mp_log *log, int lev, char *text)
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "%s", text);
|
||||
fprintf(stream, "%s%s", text, trail);
|
||||
|
||||
if (root->color)
|
||||
set_term_color(stream, -1);
|
||||
@@ -337,17 +337,17 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
char tmp[MSGSIZE_MAX];
|
||||
bool use_tmp = lev == MSGL_STATUS || lev == MSGL_STATS;
|
||||
|
||||
struct mp_log_root *root = log->root;
|
||||
char *text = use_tmp ? tmp : root->buffer;
|
||||
int len = use_tmp ? 0 : strlen(text);
|
||||
|
||||
if (vsnprintf(text + len, MSGSIZE_MAX - len, format, va) < 0)
|
||||
snprintf(text + len, MSGSIZE_MAX - len, "[fprintf error]\n");
|
||||
text[MSGSIZE_MAX - 2] = '\n';
|
||||
text[MSGSIZE_MAX - 1] = 0;
|
||||
root->buffer.len = 0;
|
||||
|
||||
if (log->partial[0])
|
||||
bstr_xappend_asprintf(root, &root->buffer, "%s", log->partial);
|
||||
log->partial[0] = '\0';
|
||||
|
||||
bstr_xappend_vasprintf(root, &root->buffer, format, va);
|
||||
|
||||
char *text = root->buffer.start;
|
||||
|
||||
if (lev == MSGL_STATS) {
|
||||
dump_stats(log, lev, text);
|
||||
@@ -366,7 +366,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
char *next = &end[1];
|
||||
char saved = next[0];
|
||||
next[0] = '\0';
|
||||
print_terminal_line(log, lev, text);
|
||||
print_terminal_line(log, lev, text, "");
|
||||
write_log_file(log, lev, text);
|
||||
write_msg_to_buffers(log, lev, text);
|
||||
next[0] = saved;
|
||||
@@ -374,23 +374,27 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
}
|
||||
|
||||
if (lev == MSGL_STATUS) {
|
||||
if (text[0]) {
|
||||
len = strlen(text);
|
||||
if (len < MSGSIZE_MAX - 1) {
|
||||
text[len] = root->termosd ? '\r' : '\n';
|
||||
text[len + 1] = '\0';
|
||||
}
|
||||
print_terminal_line(log, lev, text);
|
||||
}
|
||||
} else {
|
||||
int leftover = strlen(text);
|
||||
memmove(root->buffer, text, leftover + 1);
|
||||
if (text[0])
|
||||
print_terminal_line(log, lev, text, root->termosd ? "\r" : "\n");
|
||||
} else if (text[0]) {
|
||||
int size = strlen(text) + 1;
|
||||
if (talloc_get_size(log->partial) < size)
|
||||
log->partial = talloc_realloc(NULL, log->partial, char, size);
|
||||
memcpy(log->partial, text, size);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
}
|
||||
|
||||
static void destroy_log(void *ptr)
|
||||
{
|
||||
struct mp_log *log = ptr;
|
||||
// This is not managed via talloc itself, because mp_msg calls must be
|
||||
// thread-safe, while talloc is not thread-safe.
|
||||
talloc_free(log->partial);
|
||||
}
|
||||
|
||||
// Create a new log context, which uses talloc_ctx as talloc parent, and parent
|
||||
// as logical parent.
|
||||
// The name is the prefix put before the output. It's usually prefixed by the
|
||||
@@ -407,7 +411,9 @@ struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent,
|
||||
struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log);
|
||||
if (!parent->root)
|
||||
return log; // same as null_log
|
||||
talloc_set_destructor(log, destroy_log);
|
||||
log->root = parent->root;
|
||||
log->partial = talloc_strdup(NULL, "");
|
||||
if (name) {
|
||||
if (name[0] == '!') {
|
||||
name = &name[1];
|
||||
|
||||
+3
-5
@@ -169,11 +169,9 @@ void playlist_shuffle(struct playlist *pl)
|
||||
arr[n] = pl->first;
|
||||
playlist_unlink(pl, pl->first);
|
||||
}
|
||||
for (int n = 0; n < count; n++) {
|
||||
int other = (int)((double)(count) * rand() / (RAND_MAX + 1.0));
|
||||
struct playlist_entry *tmp = arr[n];
|
||||
arr[n] = arr[other];
|
||||
arr[other] = tmp;
|
||||
for (int n = 0; n < count - 1; n++) {
|
||||
int j = (int)((double)(count - n) * rand() / (RAND_MAX + 1.0));
|
||||
MPSWAP(struct playlist_entry *, arr[n], arr[n + j]);
|
||||
}
|
||||
for (int n = 0; n < count; n++)
|
||||
playlist_add(pl, arr[n]);
|
||||
|
||||
+67
-76
@@ -53,6 +53,7 @@ extern const demuxer_desc_t demuxer_desc_playlist;
|
||||
extern const demuxer_desc_t demuxer_desc_disc;
|
||||
extern const demuxer_desc_t demuxer_desc_rar;
|
||||
extern const demuxer_desc_t demuxer_desc_libarchive;
|
||||
extern const demuxer_desc_t demuxer_desc_null;
|
||||
extern const demuxer_desc_t demuxer_desc_timeline;
|
||||
|
||||
/* Please do not add any new demuxers here. If you want to implement a new
|
||||
@@ -76,6 +77,7 @@ const demuxer_desc_t *const demuxer_list[] = {
|
||||
&demuxer_desc_lavf,
|
||||
&demuxer_desc_mf,
|
||||
&demuxer_desc_playlist,
|
||||
&demuxer_desc_null,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -91,15 +93,13 @@ struct demux_internal {
|
||||
struct demuxer *d_buffer; // protected by lock; used to sync d_user/thread
|
||||
|
||||
// The lock protects the packet queues (struct demux_stream), d_buffer,
|
||||
// and some minor fields like thread_paused.
|
||||
// and the fields below.
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
pthread_t thread;
|
||||
|
||||
// -- All the following fields are protected by lock.
|
||||
|
||||
bool thread_paused;
|
||||
int thread_request_pause; // counter, if >0, make demuxer thread pause
|
||||
bool thread_terminate;
|
||||
bool threading;
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
@@ -130,14 +130,15 @@ struct demux_internal {
|
||||
|
||||
double ts_offset; // timestamp offset to apply everything
|
||||
|
||||
void (*run_fn)(void *); // if non-NULL, function queued to be run on
|
||||
void *run_fn_arg; // the thread as run_fn(run_fn_arg)
|
||||
|
||||
// Cached state.
|
||||
bool force_cache_update;
|
||||
double time_length;
|
||||
struct mp_tags *stream_metadata;
|
||||
struct stream_cache_info stream_cache_info;
|
||||
int64_t stream_size;
|
||||
int64_t stream_cache_size;
|
||||
int64_t stream_cache_fill;
|
||||
int stream_cache_idle;
|
||||
// Updated during init only.
|
||||
char *stream_base_filename;
|
||||
};
|
||||
@@ -631,10 +632,10 @@ static void *demux_thread(void *pctx)
|
||||
mpthread_set_name("demux");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (!in->thread_terminate) {
|
||||
in->thread_paused = in->thread_request_pause > 0;
|
||||
if (in->thread_paused) {
|
||||
if (in->run_fn) {
|
||||
in->run_fn(in->run_fn_arg);
|
||||
in->run_fn = NULL;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
continue;
|
||||
}
|
||||
if (in->tracks_switched) {
|
||||
@@ -679,11 +680,11 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
if (ts != MP_NOPTS_VALUE)
|
||||
ds->base_ts = ts;
|
||||
|
||||
if (pkt->keyframe) {
|
||||
if (pkt->keyframe && ts != MP_NOPTS_VALUE) {
|
||||
// Update bitrate - only at keyframe points, because we use the
|
||||
// (possibly) reordered packet timestamps instead of realtime.
|
||||
double d = ts - ds->last_br_ts;
|
||||
if (ts == MP_NOPTS_VALUE || ds->last_br_ts == MP_NOPTS_VALUE || d < 0) {
|
||||
if (ds->last_br_ts == MP_NOPTS_VALUE || d < 0) {
|
||||
ds->bitrate = -1;
|
||||
ds->last_br_ts = ts;
|
||||
ds->last_br_bytes = 0;
|
||||
@@ -1404,9 +1405,7 @@ static void update_cache(struct demux_internal *in)
|
||||
// Don't lock while querying the stream.
|
||||
double time_length = -1;
|
||||
struct mp_tags *stream_metadata = NULL;
|
||||
int64_t stream_cache_size = -1;
|
||||
int64_t stream_cache_fill = -1;
|
||||
int stream_cache_idle = -1;
|
||||
struct stream_cache_info stream_cache_info = {.size = -1};
|
||||
|
||||
if (demuxer->desc->control) {
|
||||
demuxer->desc->control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH,
|
||||
@@ -1415,16 +1414,12 @@ static void update_cache(struct demux_internal *in)
|
||||
|
||||
int64_t stream_size = stream_get_size(stream);
|
||||
stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata);
|
||||
stream_control(stream, STREAM_CTRL_GET_CACHE_SIZE, &stream_cache_size);
|
||||
stream_control(stream, STREAM_CTRL_GET_CACHE_FILL, &stream_cache_fill);
|
||||
stream_control(stream, STREAM_CTRL_GET_CACHE_IDLE, &stream_cache_idle);
|
||||
stream_control(stream, STREAM_CTRL_GET_CACHE_INFO, &stream_cache_info);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->time_length = time_length;
|
||||
in->stream_size = stream_size;
|
||||
in->stream_cache_size = stream_cache_size;
|
||||
in->stream_cache_fill = stream_cache_fill;
|
||||
in->stream_cache_idle = stream_cache_idle;
|
||||
in->stream_cache_info = stream_cache_info;
|
||||
if (stream_metadata) {
|
||||
talloc_free(in->stream_metadata);
|
||||
in->stream_metadata = talloc_steal(in, stream_metadata);
|
||||
@@ -1437,26 +1432,16 @@ static void update_cache(struct demux_internal *in)
|
||||
static int cached_stream_control(struct demux_internal *in, int cmd, void *arg)
|
||||
{
|
||||
// If the cache is active, wake up the thread to possibly update cache state.
|
||||
if (in->stream_cache_size >= 0) {
|
||||
if (in->stream_cache_info.size >= 0) {
|
||||
in->force_cache_update = true;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case STREAM_CTRL_GET_CACHE_SIZE:
|
||||
if (in->stream_cache_size < 0)
|
||||
case STREAM_CTRL_GET_CACHE_INFO:
|
||||
if (in->stream_cache_info.size < 0)
|
||||
return STREAM_UNSUPPORTED;
|
||||
*(int64_t *)arg = in->stream_cache_size;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_CACHE_FILL:
|
||||
if (in->stream_cache_fill < 0)
|
||||
return STREAM_UNSUPPORTED;
|
||||
*(int64_t *)arg = in->stream_cache_fill;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_CACHE_IDLE:
|
||||
if (in->stream_cache_idle < 0)
|
||||
return STREAM_UNSUPPORTED;
|
||||
*(int *)arg = in->stream_cache_idle;
|
||||
*(struct stream_cache_info *)arg = in->stream_cache_info;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_SIZE:
|
||||
if (in->stream_size < 0)
|
||||
@@ -1531,20 +1516,22 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
return DEMUXER_CTRL_DONTKNOW;
|
||||
}
|
||||
|
||||
int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
struct demux_control_args {
|
||||
struct demuxer *demuxer;
|
||||
int cmd;
|
||||
void *arg;
|
||||
int *r;
|
||||
};
|
||||
|
||||
static void thread_demux_control(void *p)
|
||||
{
|
||||
struct demux_control_args *args = p;
|
||||
struct demuxer *demuxer = args->demuxer;
|
||||
int cmd = args->cmd;
|
||||
void *arg = args->arg;
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
if (in->threading) {
|
||||
pthread_mutex_lock(&in->lock);
|
||||
int cr = cached_demux_control(in, cmd, arg);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
if (cr != DEMUXER_CTRL_DONTKNOW)
|
||||
return cr;
|
||||
}
|
||||
|
||||
int r = DEMUXER_CTRL_NOTIMPL;
|
||||
demux_pause(demuxer);
|
||||
|
||||
if (cmd == DEMUXER_CTRL_STREAM_CTRL) {
|
||||
struct demux_ctrl_stream_ctrl *c = arg;
|
||||
if (in->threading)
|
||||
@@ -1559,7 +1546,26 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
if (demuxer->desc->control)
|
||||
r = demuxer->desc->control(demuxer->in->d_thread, cmd, arg);
|
||||
}
|
||||
demux_unpause(demuxer);
|
||||
|
||||
*args->r = r;
|
||||
}
|
||||
|
||||
int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
if (in->threading) {
|
||||
pthread_mutex_lock(&in->lock);
|
||||
int cr = cached_demux_control(in, cmd, arg);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
if (cr != DEMUXER_CTRL_DONTKNOW)
|
||||
return cr;
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
struct demux_control_args args = {demuxer, cmd, arg, &r};
|
||||
demux_run_on_thread(demuxer, thread_demux_control, &args);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1570,40 +1576,25 @@ int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg)
|
||||
return c.res;
|
||||
}
|
||||
|
||||
// Make the demuxer thread stop doing anything.
|
||||
// demux_unpause() wakes up the thread again.
|
||||
// Can be nested with other calls, but trying to read packets may deadlock.
|
||||
void demux_pause(demuxer_t *demuxer)
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (!in->threading)
|
||||
return;
|
||||
|
||||
MP_VERBOSE(in, "pause demux thread\n");
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->thread_request_pause++;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (!in->thread_paused)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
void demux_unpause(demuxer_t *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (!in->threading)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
assert(in->thread_request_pause > 0);
|
||||
in->thread_request_pause--;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "blocking on demuxer thread\n");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
in->run_fn = fn;
|
||||
in->run_fn_arg = ctx;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
} else {
|
||||
fn(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer)
|
||||
|
||||
+2
-3
@@ -174,7 +174,7 @@ typedef struct demuxer {
|
||||
int64_t filepos; // input stream current pos.
|
||||
char *filename; // same as stream->url
|
||||
bool seekable;
|
||||
bool partially_seekable; // implies seekable=true
|
||||
bool partially_seekable; // true if _maybe_ seekable; implies seekable=true
|
||||
double start_time;
|
||||
// File format allows PTS resets (even if the current file is without)
|
||||
bool ts_resets_possible;
|
||||
@@ -285,8 +285,7 @@ double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_pause(demuxer_t *demuxer);
|
||||
void demux_unpause(demuxer_t *demuxer);
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
+20
-24
@@ -550,8 +550,14 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
AVFormatContext *avfc = priv->avfc;
|
||||
AVStream *st = avfc->streams[i];
|
||||
AVCodecContext *codec = st->codec;
|
||||
struct sh_stream *sh = NULL;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
AVCodecParameters *codec = st->codecpar;
|
||||
int lavc_delay = codec->initial_padding;
|
||||
#else
|
||||
AVCodecContext *codec = st->codec;
|
||||
int lavc_delay = codec->delay;
|
||||
#endif
|
||||
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_AUDIO: {
|
||||
@@ -566,7 +572,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
double delay = 0;
|
||||
if (codec->sample_rate > 0)
|
||||
delay = codec->delay / (double)codec->sample_rate;
|
||||
delay = lavc_delay / (double)codec->sample_rate;
|
||||
priv->seek_delay = MPMAX(priv->seek_delay, delay);
|
||||
|
||||
export_replaygain(demuxer, sh->codec, st);
|
||||
@@ -577,8 +583,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
sh->attached_picture = new_demux_packet_from(st->attached_pic.data,
|
||||
st->attached_pic.size);
|
||||
sh->attached_picture =
|
||||
new_demux_packet_from_avpacket(&st->attached_pic);
|
||||
if (sh->attached_picture) {
|
||||
sh->attached_picture->pts = 0;
|
||||
talloc_steal(sh, sh->attached_picture);
|
||||
@@ -588,21 +594,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
sh->codec->disp_w = codec->width;
|
||||
sh->codec->disp_h = codec->height;
|
||||
/* Try to make up some frame rate value, even if it's not reliable.
|
||||
* FPS information is needed to support subtitle formats which base
|
||||
* timing on frame numbers.
|
||||
* Libavformat seems to report no "reliable" FPS value for AVI files,
|
||||
* while they are typically constant enough FPS that the value this
|
||||
* heuristic makes up works with subtitles in practice.
|
||||
*/
|
||||
double fps;
|
||||
if (st->avg_frame_rate.num)
|
||||
fps = av_q2d(st->avg_frame_rate);
|
||||
else
|
||||
fps = 1.0 / FFMAX(av_q2d(st->time_base),
|
||||
av_q2d(st->codec->time_base) *
|
||||
st->codec->ticks_per_frame);
|
||||
sh->codec->fps = fps;
|
||||
sh->codec->fps = av_q2d(st->avg_frame_rate);
|
||||
if (priv->format_hack.image_format)
|
||||
sh->codec->fps = demuxer->opts->mf_fps;
|
||||
sh->codec->par_w = st->sample_aspect_ratio.num;
|
||||
@@ -634,12 +627,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (av_opt_get_q(avfc, "subfps", AV_OPT_SEARCH_CHILDREN, &r) >= 0) {
|
||||
// File headers don't have a FPS set.
|
||||
if (r.num < 1 || r.den < 1)
|
||||
sh->codec->frame_based = av_q2d(av_inv_q(codec->time_base));
|
||||
} else {
|
||||
// Older libavformat versions. If the FPS matches the microdvd
|
||||
// reader's default, assume it uses frame based timing.
|
||||
if (codec->time_base.num == 125 && codec->time_base.den == 2997)
|
||||
sh->codec->frame_based = 23.976;
|
||||
sh->codec->frame_based = 23.976; // default timebase
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -665,9 +653,17 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->ff_index = st->index;
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
sh->codec->lav_codecpar = avcodec_parameters_alloc();
|
||||
if (sh->codec->lav_codecpar)
|
||||
avcodec_parameters_copy(sh->codec->lav_codecpar, codec);
|
||||
#else
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
sh->codec->lav_headers = avcodec_alloc_context3(NULL);
|
||||
if (sh->codec->lav_headers)
|
||||
mp_copy_lav_codec_headers(sh->codec->lav_headers, codec);
|
||||
#endif
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
|
||||
+8
-5
@@ -152,7 +152,7 @@ typedef struct mkv_index {
|
||||
|
||||
struct block_info {
|
||||
uint64_t duration, discardpadding;
|
||||
bool simple, keyframe;
|
||||
bool simple, keyframe, duration_known;
|
||||
int64_t timecode;
|
||||
mkv_track_t *track;
|
||||
bstr data;
|
||||
@@ -1687,9 +1687,11 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_a->samplerate = 48000;
|
||||
}
|
||||
|
||||
// This field tends to be broken, and our decoder can interpolate the
|
||||
// missing timestamps anyway.
|
||||
track->default_duration = 0;
|
||||
// Some files have broken default DefaultDuration set, which will lead to
|
||||
// audio packets with incorrect timestamps. This follows FFmpeg commit
|
||||
// 6158a3b, sample see FFmpeg ticket 2508.
|
||||
if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
|
||||
track->default_duration = 0;
|
||||
|
||||
sh_a->extradata = extradata;
|
||||
sh_a->extradata_size = extradata_len;
|
||||
@@ -2461,7 +2463,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
dp->pts = current_pts + i * track->default_duration;
|
||||
if (stream->codec->avi_dts)
|
||||
MPSWAP(double, dp->pts, dp->dts);
|
||||
if (i == 0)
|
||||
if (i == 0 && block_info->duration_known)
|
||||
dp->duration = block_duration / 1e9;
|
||||
if (stream->type == STREAM_AUDIO) {
|
||||
unsigned int srate = stream->codec->samplerate;
|
||||
@@ -2504,6 +2506,7 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
|
||||
if (block->duration == EBML_UINT_INVALID)
|
||||
goto error;
|
||||
block->duration *= mkv_d->tc_scale;
|
||||
block->duration_known = true;
|
||||
break;
|
||||
|
||||
case MATROSKA_ID_DISCARDPADDING:
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 "misc/bstr.h"
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
|
||||
static int try_open_file(struct demuxer *demux, enum demux_check check)
|
||||
{
|
||||
if (!bstr_startswith0(bstr0(demux->filename), "null://") &&
|
||||
check != DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct demuxer_desc demuxer_desc_null = {
|
||||
.name = "null",
|
||||
.desc = "null demuxer",
|
||||
.open = try_open_file,
|
||||
};
|
||||
@@ -93,12 +93,10 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
double start_pts, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (p->current == new)
|
||||
return;
|
||||
bool new_segment = p->current != new;
|
||||
|
||||
if (!(flags & (SEEK_FORWARD | SEEK_BACKWARD)))
|
||||
flags |= SEEK_BACKWARD;
|
||||
flags |= SEEK_BACKWARD | SEEK_HR;
|
||||
|
||||
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
|
||||
|
||||
@@ -109,7 +107,8 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
|
||||
for (int n = 0; n < p->num_streams; n++) {
|
||||
struct virtual_stream *vs = &p->streams[n];
|
||||
vs->new_segment = true;
|
||||
if (new_segment)
|
||||
vs->new_segment = true;
|
||||
vs->eos_packets = 0;
|
||||
}
|
||||
|
||||
@@ -132,7 +131,6 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
p->current = NULL; // force seek
|
||||
switch_segment(demuxer, new, pts, flags);
|
||||
}
|
||||
|
||||
@@ -344,7 +342,7 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
print_timeline(demuxer);
|
||||
|
||||
demuxer->seekable = true;
|
||||
demuxer->partially_seekable = true;
|
||||
demuxer->partially_seekable = false;
|
||||
|
||||
demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ struct mp_codec_params {
|
||||
int extradata_size;
|
||||
|
||||
// Codec specific header data (set by demux_lavf.c only)
|
||||
// Which one is in use depends on HAVE_AVCODEC_HAS_CODECPAR.
|
||||
struct AVCodecContext *lav_headers;
|
||||
struct AVCodecParameters *lav_codecpar;
|
||||
|
||||
// STREAM_AUDIO
|
||||
int samplerate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
##
|
||||
## MPlayer-style key bindings
|
||||
##
|
||||
## Save it as ~/.config/input.conf to use it.
|
||||
## Save it as ~/.config/mpv/input.conf to use it.
|
||||
##
|
||||
## Generally, it's recommended to use this as reference-only.
|
||||
##
|
||||
|
||||
+17
-2
@@ -56,8 +56,8 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
|
||||
int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
|
||||
bstr data, enum mp_dnd_action action)
|
||||
{
|
||||
// X11 and Wayland file list format.
|
||||
if (strcmp(mime_type, "text/uri-list") == 0) {
|
||||
// (text lists are the only format supported right now)
|
||||
if (mp_event_get_mime_type_score(ictx, mime_type) >= 0) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
int num_files = 0;
|
||||
char **files = NULL;
|
||||
@@ -76,3 +76,18 @@ int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int mp_event_get_mime_type_score(struct input_ctx *ictx, const char *mime_type)
|
||||
{
|
||||
// X11 and Wayland file list format.
|
||||
if (strcmp(mime_type, "text/uri-list") == 0)
|
||||
return 10;
|
||||
// Just text; treat it the same for convenience.
|
||||
if (strcmp(mime_type, "text/plain;charset=utf-8") == 0)
|
||||
return 5;
|
||||
if (strcmp(mime_type, "text/plain") == 0)
|
||||
return 4;
|
||||
if (strcmp(mime_type, "text") == 0)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MP_INPUT_EVENT_H_
|
||||
#define MP_INPUT_EVENT_H_
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
@@ -32,3 +34,10 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
|
||||
// Returns <0 on error, ==0 if data was ok but empty, >0 on success.
|
||||
int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
|
||||
bstr data, enum mp_dnd_action append);
|
||||
|
||||
// Many drag & drop APIs support multiple mime types, and this function returns
|
||||
// whether a type is preferred (higher integer score), or supported (scores
|
||||
// below 0 indicate unsupported types).
|
||||
int mp_event_get_mime_type_score(struct input_ctx *ictx, const char *mime_type);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -625,7 +625,7 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
|
||||
}
|
||||
double now = mp_time_sec();
|
||||
// ignore system-doubleclick if we generate these events ourselves
|
||||
if (opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
|
||||
if (!force_mouse && opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
|
||||
return;
|
||||
interpret_key(ictx, code, scale);
|
||||
if (code & MP_KEY_STATE_DOWN) {
|
||||
|
||||
@@ -257,4 +257,13 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global);
|
||||
void mp_uninit_ipc(struct mp_ipc_ctx *ctx);
|
||||
|
||||
// Serialize the given mpv_event structure to JSON. Returns an allocated string.
|
||||
struct mpv_event;
|
||||
char *mp_json_encode_event(struct mpv_event *event);
|
||||
|
||||
// Given the raw IPC input buffer "buf", remove the first newline-separated
|
||||
// command, execute it and return the result (if any) as an allocated string.
|
||||
struct mpv_handle;
|
||||
char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf);
|
||||
|
||||
#endif /* MPLAYER_INPUT_H */
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* 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 <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/threads.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "input/input.h"
|
||||
#include "libmpv/client.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "player/client.h"
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
struct mp_ipc_ctx {
|
||||
struct mp_log *log;
|
||||
struct mp_client_api *client_api;
|
||||
const char *path;
|
||||
|
||||
pthread_t thread;
|
||||
int death_pipe[2];
|
||||
};
|
||||
|
||||
struct client_arg {
|
||||
struct mp_log *log;
|
||||
struct mpv_handle *client;
|
||||
|
||||
char *client_name;
|
||||
int client_fd;
|
||||
bool close_client_fd;
|
||||
|
||||
bool writable;
|
||||
};
|
||||
|
||||
static int ipc_write_str(struct client_arg *client, const char *buf)
|
||||
{
|
||||
size_t count = strlen(buf);
|
||||
while (count > 0) {
|
||||
ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL);
|
||||
if (rc <= 0) {
|
||||
if (rc == 0)
|
||||
return -1;
|
||||
|
||||
if (errno == EBADF) {
|
||||
client->writable = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (errno == EAGAIN)
|
||||
return 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
count -= rc;
|
||||
buf += rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *client_thread(void *p)
|
||||
{
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
int rc;
|
||||
|
||||
struct client_arg *arg = p;
|
||||
bstr client_msg = { talloc_strdup(NULL, ""), 0 };
|
||||
|
||||
mpthread_set_name(arg->client_name);
|
||||
|
||||
int pipe_fd = mpv_get_wakeup_pipe(arg->client);
|
||||
if (pipe_fd < 0) {
|
||||
MP_ERR(arg, "Could not get wakeup pipe\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
MP_VERBOSE(arg, "Client connected\n");
|
||||
|
||||
struct pollfd fds[2] = {
|
||||
{.events = POLLIN, .fd = pipe_fd},
|
||||
{.events = POLLIN, .fd = arg->client_fd},
|
||||
};
|
||||
|
||||
fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
mpv_suspend(arg->client);
|
||||
|
||||
while (1) {
|
||||
rc = poll(fds, 2, 0);
|
||||
if (rc == 0) {
|
||||
mpv_resume(arg->client);
|
||||
rc = poll(fds, 2, -1);
|
||||
mpv_suspend(arg->client);
|
||||
}
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Poll error\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
char discard[100];
|
||||
read(pipe_fd, discard, sizeof(discard));
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
|
||||
if (event->event_id == MPV_EVENT_SHUTDOWN)
|
||||
goto done;
|
||||
|
||||
if (!arg->writable)
|
||||
continue;
|
||||
|
||||
char *event_msg = mp_json_encode_event(event);
|
||||
if (!event_msg) {
|
||||
MP_ERR(arg, "Encoding error\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = ipc_write_str(arg, event_msg);
|
||||
talloc_free(event_msg);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fds[1].revents & (POLLIN | POLLHUP)) {
|
||||
while (1) {
|
||||
char buf[128];
|
||||
bstr append = { buf, 0 };
|
||||
|
||||
ssize_t bytes = read(arg->client_fd, buf, sizeof(buf));
|
||||
if (bytes < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (bytes == 0) {
|
||||
MP_VERBOSE(arg, "Client disconnected\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
append.len = bytes;
|
||||
|
||||
bstr_xappend(NULL, &client_msg, append);
|
||||
|
||||
while (bstrchr(client_msg, '\n') != -1) {
|
||||
char *reply_msg = mp_ipc_consume_next_command(arg->client,
|
||||
NULL, &client_msg);
|
||||
|
||||
if (reply_msg && arg->writable) {
|
||||
rc = ipc_write_str(arg, reply_msg);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno));
|
||||
talloc_free(reply_msg);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(reply_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (client_msg.len > 0)
|
||||
MP_WARN(arg, "Ignoring unterminated command on disconnect.\n");
|
||||
talloc_free(client_msg.start);
|
||||
if (arg->close_client_fd)
|
||||
close(arg->client_fd);
|
||||
mpv_detach_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
|
||||
{
|
||||
client->client = mp_new_client(ctx->client_api, client->client_name),
|
||||
client->log = mp_client_get_log(client->client);
|
||||
|
||||
pthread_t client_thr;
|
||||
if (pthread_create(&client_thr, NULL, client_thread, client)) {
|
||||
mpv_detach_destroy(client->client);
|
||||
if (client->close_client_fd)
|
||||
close(client->client_fd);
|
||||
talloc_free(client);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
|
||||
{
|
||||
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||
*client = (struct client_arg){
|
||||
.client_name = talloc_asprintf(client, "ipc-%d", id),
|
||||
.client_fd = fd,
|
||||
.close_client_fd = true,
|
||||
|
||||
.writable = true,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
}
|
||||
|
||||
static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
||||
{
|
||||
int mode = O_RDONLY;
|
||||
int client_fd = -1;
|
||||
bool close_client_fd = true;
|
||||
bool writable = false;
|
||||
|
||||
if (strcmp(path, "/dev/stdin") == 0) { // for symmetry with Linux
|
||||
client_fd = STDIN_FILENO;
|
||||
close_client_fd = false;
|
||||
} else if (strncmp(path, "fd://", 5) == 0) {
|
||||
char *end = NULL;
|
||||
client_fd = strtol(path + 5, &end, 0);
|
||||
if (!end || end == path + 5 || end[0]) {
|
||||
MP_ERR(ctx, "Invalid FD: %s\n", path);
|
||||
return;
|
||||
}
|
||||
close_client_fd = false;
|
||||
writable = true; // maybe
|
||||
} else {
|
||||
// Use RDWR for FIFOs to ensure they stay open over multiple accesses.
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0 && S_ISFIFO(st.st_mode))
|
||||
mode = O_RDWR;
|
||||
client_fd = open(path, mode);
|
||||
}
|
||||
if (client_fd < 0) {
|
||||
MP_ERR(ctx, "Could not open '%s'\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||
*client = (struct client_arg){
|
||||
.client_name = "input-file",
|
||||
.client_fd = client_fd,
|
||||
.close_client_fd = close_client_fd,
|
||||
.writable = writable,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
}
|
||||
|
||||
static void *ipc_thread(void *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
int ipc_fd;
|
||||
struct sockaddr_un ipc_un = {0};
|
||||
|
||||
struct mp_ipc_ctx *arg = p;
|
||||
|
||||
mpthread_set_name("ipc socket listener");
|
||||
|
||||
MP_VERBOSE(arg, "Starting IPC master\n");
|
||||
|
||||
ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ipc_fd < 0) {
|
||||
MP_ERR(arg, "Could not create IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if HAVE_FCHMOD
|
||||
fchmod(ipc_fd, 0600);
|
||||
#endif
|
||||
|
||||
size_t path_len = strlen(arg->path);
|
||||
if (path_len >= sizeof(ipc_un.sun_path) - 1) {
|
||||
MP_ERR(arg, "Could not create IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_un.sun_family = AF_UNIX,
|
||||
strncpy(ipc_un.sun_path, arg->path, sizeof(ipc_un.sun_path));
|
||||
|
||||
unlink(ipc_un.sun_path);
|
||||
|
||||
if (ipc_un.sun_path[0] == '@') {
|
||||
ipc_un.sun_path[0] = '\0';
|
||||
path_len--;
|
||||
}
|
||||
|
||||
size_t addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + path_len;
|
||||
rc = bind(ipc_fd, (struct sockaddr *) &ipc_un, addr_len);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Could not bind IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = listen(ipc_fd, 10);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Could not listen on IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
int client_num = 0;
|
||||
|
||||
struct pollfd fds[2] = {
|
||||
{.events = POLLIN, .fd = arg->death_pipe[0]},
|
||||
{.events = POLLIN, .fd = ipc_fd},
|
||||
};
|
||||
|
||||
while (1) {
|
||||
rc = poll(fds, 2, -1);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Poll error\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
goto done;
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
int client_fd = accept(ipc_fd, NULL, NULL);
|
||||
if (client_fd < 0) {
|
||||
MP_ERR(arg, "Could not accept IPC client\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_start_client_json(arg, client_num++, client_fd);
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (ipc_fd >= 0)
|
||||
close(ipc_fd);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
|
||||
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
|
||||
*arg = (struct mp_ipc_ctx){
|
||||
.log = mp_log_new(arg, global->log, "ipc"),
|
||||
.client_api = client_api,
|
||||
.path = mp_get_user_path(arg, global, opts->ipc_path),
|
||||
.death_pipe = {-1, -1},
|
||||
};
|
||||
char *input_file = mp_get_user_path(arg, global, opts->input_file);
|
||||
|
||||
if (input_file && *input_file)
|
||||
ipc_start_client_text(arg, input_file);
|
||||
|
||||
if (!opts->ipc_path || !*opts->ipc_path)
|
||||
goto out;
|
||||
|
||||
if (mp_make_wakeup_pipe(arg->death_pipe) < 0)
|
||||
goto out;
|
||||
|
||||
if (pthread_create(&arg->thread, NULL, ipc_thread, arg))
|
||||
goto out;
|
||||
|
||||
return arg;
|
||||
|
||||
out:
|
||||
close(arg->death_pipe[0]);
|
||||
close(arg->death_pipe[1]);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_uninit_ipc(struct mp_ipc_ctx *arg)
|
||||
{
|
||||
if (!arg)
|
||||
return;
|
||||
|
||||
write(arg->death_pipe[1], &(char){0}, 1);
|
||||
pthread_join(arg->thread, NULL);
|
||||
|
||||
close(arg->death_pipe[0]);
|
||||
close(arg->death_pipe[1]);
|
||||
talloc_free(arg);
|
||||
}
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* 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 <windows.h>
|
||||
#include <sddl.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "input/input.h"
|
||||
#include "libmpv/client.h"
|
||||
#include "options/options.h"
|
||||
#include "player/client.h"
|
||||
|
||||
struct mp_ipc_ctx {
|
||||
struct mp_log *log;
|
||||
struct mp_client_api *client_api;
|
||||
const wchar_t *path;
|
||||
|
||||
pthread_t thread;
|
||||
HANDLE death_event;
|
||||
};
|
||||
|
||||
struct client_arg {
|
||||
struct mp_log *log;
|
||||
struct mpv_handle *client;
|
||||
|
||||
char *client_name;
|
||||
HANDLE client_h;
|
||||
bool writable;
|
||||
OVERLAPPED write_ol;
|
||||
};
|
||||
|
||||
// Get a string SID representing the current user. Must be freed by LocalFree.
|
||||
static char *get_user_sid(void)
|
||||
{
|
||||
char *ssid = NULL;
|
||||
TOKEN_USER *info = NULL;
|
||||
HANDLE t;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
|
||||
goto done;
|
||||
|
||||
DWORD info_len;
|
||||
if (!GetTokenInformation(t, TokenUser, NULL, 0, &info_len) &&
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
goto done;
|
||||
|
||||
info = talloc_size(NULL, info_len);
|
||||
if (!GetTokenInformation(t, TokenUser, info, info_len, &info_len))
|
||||
goto done;
|
||||
if (!info->User.Sid)
|
||||
goto done;
|
||||
|
||||
ConvertSidToStringSidA(info->User.Sid, &ssid);
|
||||
done:
|
||||
if (t)
|
||||
CloseHandle(t);
|
||||
talloc_free(info);
|
||||
return ssid;
|
||||
}
|
||||
|
||||
// Get a string SID for the process integrity level. Must be freed by LocalFree.
|
||||
static char *get_integrity_sid(void)
|
||||
{
|
||||
char *ssid = NULL;
|
||||
TOKEN_MANDATORY_LABEL *info = NULL;
|
||||
HANDLE t;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
|
||||
goto done;
|
||||
|
||||
DWORD info_len;
|
||||
if (!GetTokenInformation(t, TokenIntegrityLevel, NULL, 0, &info_len) &&
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
goto done;
|
||||
|
||||
info = talloc_size(NULL, info_len);
|
||||
if (!GetTokenInformation(t, TokenIntegrityLevel, info, info_len, &info_len))
|
||||
goto done;
|
||||
if (!info->Label.Sid)
|
||||
goto done;
|
||||
|
||||
ConvertSidToStringSidA(info->Label.Sid, &ssid);
|
||||
done:
|
||||
if (t)
|
||||
CloseHandle(t);
|
||||
talloc_free(info);
|
||||
return ssid;
|
||||
}
|
||||
|
||||
// Create a security descriptor that only grants access to processes running
|
||||
// under the current user at the current integrity level or higher
|
||||
static PSECURITY_DESCRIPTOR create_restricted_sd(void)
|
||||
{
|
||||
char *user_sid = get_user_sid();
|
||||
char *integrity_sid = get_integrity_sid();
|
||||
if (!user_sid || !integrity_sid)
|
||||
return NULL;
|
||||
|
||||
char *sddl = talloc_asprintf(NULL,
|
||||
"O:%s" // Set the owner to user_sid
|
||||
"D:(A;;GRGW;;;%s)" // Grant GENERIC_{READ,WRITE} access to user_sid
|
||||
"S:(ML;;NRNWNX;;;%s)", // Disallow read, write and execute permissions
|
||||
// to integrity levels below integrity_sid
|
||||
user_sid, user_sid, integrity_sid);
|
||||
LocalFree(user_sid);
|
||||
LocalFree(integrity_sid);
|
||||
|
||||
PSECURITY_DESCRIPTOR sd = NULL;
|
||||
ConvertStringSecurityDescriptorToSecurityDescriptorA(sddl, SDDL_REVISION_1,
|
||||
&sd, NULL);
|
||||
talloc_free(sddl);
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static void wakeup_cb(void *d)
|
||||
{
|
||||
HANDLE event = d;
|
||||
SetEvent(event);
|
||||
}
|
||||
|
||||
// Wrapper for ReadFile that treats ERROR_IO_PENDING as success
|
||||
static DWORD async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol)
|
||||
{
|
||||
DWORD err = ReadFile(file, buf, size, NULL, ol) ? 0 : GetLastError();
|
||||
return err == ERROR_IO_PENDING ? 0 : err;
|
||||
}
|
||||
|
||||
// Wrapper for WriteFile that treats ERROR_IO_PENDING as success
|
||||
static DWORD async_write(HANDLE file, const void *buf, unsigned size, OVERLAPPED* ol)
|
||||
{
|
||||
DWORD err = WriteFile(file, buf, size, NULL, ol) ? 0 : GetLastError();
|
||||
return err == ERROR_IO_PENDING ? 0 : err;
|
||||
}
|
||||
|
||||
static bool pipe_error_is_fatal(DWORD error)
|
||||
{
|
||||
switch (error) {
|
||||
case 0:
|
||||
case ERROR_HANDLE_EOF:
|
||||
case ERROR_BROKEN_PIPE:
|
||||
case ERROR_PIPE_NOT_CONNECTED:
|
||||
case ERROR_NO_DATA:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static DWORD ipc_write_str(struct client_arg *arg, const char *buf)
|
||||
{
|
||||
DWORD error = 0;
|
||||
|
||||
if ((error = async_write(arg->client_h, buf, strlen(buf), &arg->write_ol)))
|
||||
goto done;
|
||||
if (!GetOverlappedResult(arg->client_h, &arg->write_ol, &(DWORD){0}, TRUE)) {
|
||||
error = GetLastError();
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
if (pipe_error_is_fatal(error)) {
|
||||
MP_VERBOSE(arg, "Error writing to pipe: %s\n",
|
||||
mp_HRESULT_to_str(HRESULT_FROM_WIN32(error)));
|
||||
}
|
||||
|
||||
if (error)
|
||||
arg->writable = false;
|
||||
return error;
|
||||
}
|
||||
|
||||
static void report_read_error(struct client_arg *arg, DWORD error)
|
||||
{
|
||||
// Only report the error if it's not just due to the pipe closing
|
||||
if (pipe_error_is_fatal(error)) {
|
||||
MP_ERR(arg, "Error reading from pipe: %s\n",
|
||||
mp_HRESULT_to_str(HRESULT_FROM_WIN32(error)));
|
||||
} else {
|
||||
MP_VERBOSE(arg, "Client disconnected\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void *client_thread(void *p)
|
||||
{
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
struct client_arg *arg = p;
|
||||
char buf[4096];
|
||||
HANDLE wakeup_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) };
|
||||
bstr client_msg = { talloc_strdup(NULL, ""), 0 };
|
||||
DWORD ioerr = 0;
|
||||
DWORD r;
|
||||
|
||||
mpthread_set_name(arg->client_name);
|
||||
|
||||
arg->write_ol.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
|
||||
if (!wakeup_event || !ol.hEvent || !arg->write_ol.hEvent) {
|
||||
MP_ERR(arg, "Couldn't create events\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
MP_VERBOSE(arg, "Client connected\n");
|
||||
|
||||
mpv_set_wakeup_callback(arg->client, wakeup_cb, wakeup_event);
|
||||
mpv_suspend(arg->client);
|
||||
|
||||
// Do the first read operation on the pipe
|
||||
if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) {
|
||||
report_read_error(arg, ioerr);
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
HANDLE handles[] = { wakeup_event, ol.hEvent };
|
||||
int n = WaitForMultipleObjects(2, handles, FALSE, 0);
|
||||
if (n == WAIT_TIMEOUT) {
|
||||
mpv_resume(arg->client);
|
||||
n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||||
mpv_suspend(arg->client);
|
||||
}
|
||||
|
||||
switch (n) {
|
||||
case WAIT_OBJECT_0: // wakeup_event
|
||||
ResetEvent(wakeup_event);
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
|
||||
if (event->event_id == MPV_EVENT_SHUTDOWN)
|
||||
goto done;
|
||||
|
||||
if (!arg->writable)
|
||||
continue;
|
||||
|
||||
char *event_msg = mp_json_encode_event(event);
|
||||
if (!event_msg) {
|
||||
MP_ERR(arg, "Encoding error\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_write_str(arg, event_msg);
|
||||
talloc_free(event_msg);
|
||||
}
|
||||
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1: // ol.hEvent
|
||||
// Complete the read operation on the pipe
|
||||
if (!GetOverlappedResult(arg->client_h, &ol, &r, TRUE)) {
|
||||
report_read_error(arg, GetLastError());
|
||||
goto done;
|
||||
}
|
||||
|
||||
bstr_xappend(NULL, &client_msg, (bstr){buf, r});
|
||||
while (bstrchr(client_msg, '\n') != -1) {
|
||||
char *reply_msg = mp_ipc_consume_next_command(arg->client,
|
||||
NULL, &client_msg);
|
||||
if (reply_msg && arg->writable)
|
||||
ipc_write_str(arg, reply_msg);
|
||||
talloc_free(reply_msg);
|
||||
}
|
||||
|
||||
// Begin the next read operation on the pipe
|
||||
if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) {
|
||||
report_read_error(arg, ioerr);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MP_ERR(arg, "WaitForMultipleObjects failed\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (client_msg.len > 0)
|
||||
MP_WARN(arg, "Ignoring unterminated command on disconnect.\n");
|
||||
|
||||
if (CancelIoEx(arg->client_h, &ol) || GetLastError() != ERROR_NOT_FOUND)
|
||||
GetOverlappedResult(arg->client_h, &ol, &(DWORD){0}, TRUE);
|
||||
if (wakeup_event)
|
||||
CloseHandle(wakeup_event);
|
||||
if (ol.hEvent)
|
||||
CloseHandle(ol.hEvent);
|
||||
if (arg->write_ol.hEvent)
|
||||
CloseHandle(arg->write_ol.hEvent);
|
||||
|
||||
CloseHandle(arg->client_h);
|
||||
mpv_detach_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
|
||||
{
|
||||
client->client = mp_new_client(ctx->client_api, client->client_name),
|
||||
client->log = mp_client_get_log(client->client);
|
||||
|
||||
pthread_t client_thr;
|
||||
if (pthread_create(&client_thr, NULL, client_thread, client)) {
|
||||
mpv_detach_destroy(client->client);
|
||||
CloseHandle(client->client_h);
|
||||
talloc_free(client);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h)
|
||||
{
|
||||
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||
*client = (struct client_arg){
|
||||
.client_name = talloc_asprintf(client, "ipc-%d", id),
|
||||
.client_h = h,
|
||||
.writable = true,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
}
|
||||
|
||||
static void *ipc_thread(void *p)
|
||||
{
|
||||
// Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is
|
||||
// maintained for message-mode clients, but byte-mode clients can still
|
||||
// connect, send and receive data. This is the most compatible mode.
|
||||
static const DWORD state =
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |
|
||||
PIPE_REJECT_REMOTE_CLIENTS;
|
||||
static const DWORD mode =
|
||||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
|
||||
static const DWORD bufsiz = 4096;
|
||||
|
||||
struct mp_ipc_ctx *arg = p;
|
||||
HANDLE server = INVALID_HANDLE_VALUE;
|
||||
HANDLE client = INVALID_HANDLE_VALUE;
|
||||
int client_num = 0;
|
||||
|
||||
mpthread_set_name("ipc named pipe listener");
|
||||
MP_VERBOSE(arg, "Starting IPC master\n");
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {
|
||||
.nLength = sizeof sa,
|
||||
.lpSecurityDescriptor = create_restricted_sd(),
|
||||
};
|
||||
if (!sa.lpSecurityDescriptor) {
|
||||
MP_ERR(arg, "Couldn't create security descriptor");
|
||||
goto done;
|
||||
}
|
||||
|
||||
OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) };
|
||||
if (!ol.hEvent) {
|
||||
MP_ERR(arg, "Couldn't create event");
|
||||
goto done;
|
||||
}
|
||||
|
||||
server = CreateNamedPipeW(arg->path, mode | FILE_FLAG_FIRST_PIPE_INSTANCE,
|
||||
state, PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa);
|
||||
if (server == INVALID_HANDLE_VALUE) {
|
||||
MP_ERR(arg, "Couldn't create first pipe instance: %s\n",
|
||||
mp_LastError_to_str());
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
DWORD err = ConnectNamedPipe(server, &ol) ? 0 : GetLastError();
|
||||
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
int n = WaitForMultipleObjects(2, (HANDLE[]) {
|
||||
arg->death_event,
|
||||
ol.hEvent,
|
||||
}, FALSE, INFINITE) - WAIT_OBJECT_0;
|
||||
|
||||
switch (n) {
|
||||
case 0:
|
||||
// Stop waiting for new clients
|
||||
CancelIo(server);
|
||||
GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE);
|
||||
goto done;
|
||||
case 1:
|
||||
// Complete the ConnectNamedPipe request
|
||||
err = GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE)
|
||||
? 0 : GetLastError();
|
||||
break;
|
||||
default:
|
||||
MP_ERR(arg, "WaitForMultipleObjects failed\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
// ERROR_PIPE_CONNECTED is returned if a client connects before
|
||||
// ConnectNamedPipe is called. ERROR_NO_DATA is returned if a client
|
||||
// connects, (possibly) writes data and exits before ConnectNamedPipe
|
||||
// is called. Both cases should be handled as normal connections.
|
||||
if (err == ERROR_PIPE_CONNECTED || err == ERROR_NO_DATA)
|
||||
err = 0;
|
||||
|
||||
if (err) {
|
||||
MP_ERR(arg, "ConnectNamedPipe failed: %s\n",
|
||||
mp_HRESULT_to_str(HRESULT_FROM_WIN32(err)));
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Create the next pipe instance before the client thread to avoid the
|
||||
// theoretical race condition where the client thread immediately
|
||||
// closes the handle and there are no active instances of the pipe
|
||||
client = server;
|
||||
server = CreateNamedPipeW(arg->path, mode, state,
|
||||
PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa);
|
||||
if (server == INVALID_HANDLE_VALUE) {
|
||||
MP_ERR(arg, "Couldn't create additional pipe instance: %s\n",
|
||||
mp_LastError_to_str());
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_start_client_json(arg, client_num++, client);
|
||||
client = NULL;
|
||||
}
|
||||
|
||||
done:
|
||||
if (sa.lpSecurityDescriptor)
|
||||
LocalFree(sa.lpSecurityDescriptor);
|
||||
if (client != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(client);
|
||||
if (server != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(server);
|
||||
if (ol.hEvent)
|
||||
CloseHandle(ol.hEvent);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
|
||||
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
|
||||
*arg = (struct mp_ipc_ctx){
|
||||
.log = mp_log_new(arg, global->log, "ipc"),
|
||||
.client_api = client_api,
|
||||
};
|
||||
|
||||
if (!opts->ipc_path || !*opts->ipc_path)
|
||||
goto out;
|
||||
|
||||
// Ensure the path is a legal Win32 pipe name by prepending \\.\pipe\ if
|
||||
// it's not already present. Qt's QLocalSocket uses the same logic, so
|
||||
// cross-platform programs that use paths like /tmp/mpv-socket should just
|
||||
// work. (Win32 converts this path to \Device\NamedPipe\tmp\mpv-socket)
|
||||
if (!strncmp(opts->ipc_path, "\\\\.\\pipe\\", 9)) {
|
||||
arg->path = mp_from_utf8(arg, opts->ipc_path);
|
||||
} else {
|
||||
char *path = talloc_asprintf(NULL, "\\\\.\\pipe\\%s", opts->ipc_path);
|
||||
arg->path = mp_from_utf8(arg, path);
|
||||
talloc_free(path);
|
||||
}
|
||||
|
||||
if (!(arg->death_event = CreateEventW(NULL, TRUE, FALSE, NULL)))
|
||||
goto out;
|
||||
|
||||
if (pthread_create(&arg->thread, NULL, ipc_thread, arg))
|
||||
goto out;
|
||||
|
||||
return arg;
|
||||
|
||||
out:
|
||||
if (arg->death_event)
|
||||
CloseHandle(arg->death_event);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_uninit_ipc(struct mp_ipc_ctx *arg)
|
||||
{
|
||||
if (!arg)
|
||||
return;
|
||||
|
||||
SetEvent(arg->death_event);
|
||||
pthread_join(arg->thread, NULL);
|
||||
|
||||
CloseHandle(arg->death_event);
|
||||
talloc_free(arg);
|
||||
}
|
||||
+39
-427
@@ -15,58 +15,16 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/threads.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "input/input.h"
|
||||
#include "libmpv/client.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/json.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "player/client.h"
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
struct mp_ipc_ctx {
|
||||
struct mp_log *log;
|
||||
struct mp_client_api *client_api;
|
||||
const char *path;
|
||||
|
||||
pthread_t thread;
|
||||
int death_pipe[2];
|
||||
};
|
||||
|
||||
struct client_arg {
|
||||
struct mp_log *log;
|
||||
struct mpv_handle *client;
|
||||
|
||||
char *client_name;
|
||||
int client_fd;
|
||||
bool close_client_fd;
|
||||
|
||||
bool writable;
|
||||
};
|
||||
|
||||
static mpv_node *mpv_node_map_get(mpv_node *src, const char *key)
|
||||
{
|
||||
if (src->format != MPV_FORMAT_NODE_MAP)
|
||||
@@ -219,7 +177,7 @@ static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
|
||||
}
|
||||
}
|
||||
|
||||
static char *json_encode_event(mpv_event *event)
|
||||
char *mp_json_encode_event(mpv_event *event)
|
||||
{
|
||||
void *ta_parent = talloc_new(NULL);
|
||||
mpv_node event_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
|
||||
@@ -236,11 +194,12 @@ static char *json_encode_event(mpv_event *event)
|
||||
}
|
||||
|
||||
// Function is allowed to modify src[n].
|
||||
static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
|
||||
char *src)
|
||||
{
|
||||
int rc;
|
||||
const char *cmd = NULL;
|
||||
struct mp_log *log = mp_client_get_log(client);
|
||||
|
||||
mpv_node msg_node;
|
||||
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
|
||||
@@ -248,7 +207,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
|
||||
rc = json_parse(ta_parent, &msg_node, &src, 3);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "malformed JSON received\n");
|
||||
mp_err(log, "malformed JSON received\n");
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
goto error;
|
||||
}
|
||||
@@ -278,11 +237,11 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
cmd = cmd_str_node->u.string;
|
||||
|
||||
if (!strcmp("client_name", cmd)) {
|
||||
const char *client_name = mpv_client_name(arg->client);
|
||||
const char *client_name = mpv_client_name(client);
|
||||
mpv_node_map_add_string(ta_parent, &reply_node, "data", client_name);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else if (!strcmp("get_time_us", cmd)) {
|
||||
int64_t time_us = mpv_get_time_us(arg->client);
|
||||
int64_t time_us = mpv_get_time_us(client);
|
||||
mpv_node_map_add_int64(ta_parent, &reply_node, "data", time_us);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else if (!strcmp("get_version", cmd)) {
|
||||
@@ -302,7 +261,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_get_property(arg->client, cmd_node->u.list->values[1].u.string,
|
||||
rc = mpv_get_property(client, cmd_node->u.list->values[1].u.string,
|
||||
MPV_FORMAT_NODE, &result_node);
|
||||
if (rc >= 0) {
|
||||
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
|
||||
@@ -319,7 +278,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *result = mpv_get_property_string(arg->client,
|
||||
char *result = mpv_get_property_string(client,
|
||||
cmd_node->u.list->values[1].u.string);
|
||||
if (!result) {
|
||||
mpv_node_map_add_null(ta_parent, &reply_node, "data");
|
||||
@@ -338,7 +297,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_set_property(arg->client, cmd_node->u.list->values[1].u.string,
|
||||
rc = mpv_set_property(client, cmd_node->u.list->values[1].u.string,
|
||||
MPV_FORMAT_NODE, &cmd_node->u.list->values[2]);
|
||||
} else if (!strcmp("set_property_string", cmd)) {
|
||||
if (cmd_node->u.list->num != 3) {
|
||||
@@ -356,7 +315,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_set_property_string(arg->client,
|
||||
rc = mpv_set_property_string(client,
|
||||
cmd_node->u.list->values[1].u.string,
|
||||
cmd_node->u.list->values[2].u.string);
|
||||
} else if (!strcmp("observe_property", cmd)) {
|
||||
@@ -375,7 +334,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_observe_property(arg->client,
|
||||
rc = mpv_observe_property(client,
|
||||
cmd_node->u.list->values[1].u.int64,
|
||||
cmd_node->u.list->values[2].u.string,
|
||||
MPV_FORMAT_NODE);
|
||||
@@ -395,7 +354,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_observe_property(arg->client,
|
||||
rc = mpv_observe_property(client,
|
||||
cmd_node->u.list->values[1].u.int64,
|
||||
cmd_node->u.list->values[2].u.string,
|
||||
MPV_FORMAT_STRING);
|
||||
@@ -410,8 +369,8 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_unobserve_property(arg->client,
|
||||
cmd_node->u.list->values[1].u.int64);
|
||||
rc = mpv_unobserve_property(client,
|
||||
cmd_node->u.list->values[1].u.int64);
|
||||
} else if (!strcmp("request_log_messages", cmd)) {
|
||||
if (cmd_node->u.list->num != 2) {
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
@@ -423,13 +382,13 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = mpv_request_log_messages(arg->client,
|
||||
rc = mpv_request_log_messages(client,
|
||||
cmd_node->u.list->values[1].u.string);
|
||||
} else if (!strcmp("suspend", cmd)) {
|
||||
mpv_suspend(arg->client);
|
||||
mpv_suspend(client);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else if (!strcmp("resume", cmd)) {
|
||||
mpv_resume(arg->client);
|
||||
mpv_resume(client);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else if (!strcmp("enable_event", cmd) ||
|
||||
!strcmp("disable_event", cmd))
|
||||
@@ -449,7 +408,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
char *name = cmd_node->u.list->values[1].u.string;
|
||||
if (strcmp(name, "all") == 0) {
|
||||
for (int n = 0; n < 64; n++)
|
||||
mpv_request_event(arg->client, n, enable);
|
||||
mpv_request_event(client, n, enable);
|
||||
rc = MPV_ERROR_SUCCESS;
|
||||
} else {
|
||||
int event = -1;
|
||||
@@ -462,12 +421,12 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
goto error;
|
||||
}
|
||||
rc = mpv_request_event(arg->client, event, enable);
|
||||
rc = mpv_request_event(client, event, enable);
|
||||
}
|
||||
} else {
|
||||
mpv_node result_node;
|
||||
|
||||
rc = mpv_command_node(arg->client, cmd_node, &result_node);
|
||||
rc = mpv_command_node(client, cmd_node, &result_node);
|
||||
if (rc >= 0)
|
||||
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
|
||||
}
|
||||
@@ -490,382 +449,35 @@ error:
|
||||
return output;
|
||||
}
|
||||
|
||||
static char *text_execute_command(struct client_arg *arg, void *tmp, char *src)
|
||||
static char *text_execute_command(struct mpv_handle *client, void *tmp, char *src)
|
||||
{
|
||||
mpv_command_string(arg->client, src);
|
||||
mpv_command_string(client, src);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ipc_write_str(struct client_arg *client, const char *buf)
|
||||
char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf)
|
||||
{
|
||||
size_t count = strlen(buf);
|
||||
while (count > 0) {
|
||||
ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL);
|
||||
if (rc <= 0) {
|
||||
if (rc == 0)
|
||||
return -1;
|
||||
void *tmp = talloc_new(NULL);
|
||||
|
||||
if (errno == EBADF) {
|
||||
client->writable = false;
|
||||
return 0;
|
||||
}
|
||||
bstr rest;
|
||||
bstr line = bstr_getline(*buf, &rest);
|
||||
char *line0 = bstrto0(tmp, line);
|
||||
talloc_steal(tmp, buf->start);
|
||||
*buf = bstrdup(NULL, rest);
|
||||
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
json_skip_whitespace(&line0);
|
||||
|
||||
if (errno == EAGAIN)
|
||||
return 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
count -= rc;
|
||||
buf += rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *client_thread(void *p)
|
||||
{
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
int rc;
|
||||
|
||||
struct client_arg *arg = p;
|
||||
bstr client_msg = { talloc_strdup(NULL, ""), 0 };
|
||||
|
||||
mpthread_set_name(arg->client_name);
|
||||
|
||||
int pipe_fd = mpv_get_wakeup_pipe(arg->client);
|
||||
if (pipe_fd < 0) {
|
||||
MP_ERR(arg, "Could not get wakeup pipe\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
MP_VERBOSE(arg, "Client connected\n");
|
||||
|
||||
struct pollfd fds[2] = {
|
||||
{.events = POLLIN, .fd = pipe_fd},
|
||||
{.events = POLLIN, .fd = arg->client_fd},
|
||||
};
|
||||
|
||||
fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
mpv_suspend(arg->client);
|
||||
|
||||
while (1) {
|
||||
rc = poll(fds, 2, 0);
|
||||
if (rc == 0) {
|
||||
mpv_resume(arg->client);
|
||||
rc = poll(fds, 2, -1);
|
||||
mpv_suspend(arg->client);
|
||||
}
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Poll error\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
char discard[100];
|
||||
read(pipe_fd, discard, sizeof(discard));
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
|
||||
if (event->event_id == MPV_EVENT_SHUTDOWN)
|
||||
goto done;
|
||||
|
||||
if (!arg->writable)
|
||||
continue;
|
||||
|
||||
char *event_msg = json_encode_event(event);
|
||||
if (!event_msg) {
|
||||
MP_ERR(arg, "Encoding error\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = ipc_write_str(arg, event_msg);
|
||||
talloc_free(event_msg);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fds[1].revents & (POLLIN | POLLHUP)) {
|
||||
while (1) {
|
||||
char buf[128];
|
||||
bstr append = { buf, 0 };
|
||||
|
||||
ssize_t bytes = read(arg->client_fd, buf, sizeof(buf));
|
||||
if (bytes < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (bytes == 0) {
|
||||
MP_VERBOSE(arg, "Client disconnected\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
append.len = bytes;
|
||||
|
||||
bstr_xappend(NULL, &client_msg, append);
|
||||
|
||||
while (bstrchr(client_msg, '\n') != -1) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
bstr rest;
|
||||
bstr line = bstr_getline(client_msg, &rest);
|
||||
char *line0 = bstrto0(tmp, line);
|
||||
talloc_steal(tmp, client_msg.start);
|
||||
client_msg = bstrdup(NULL, rest);
|
||||
|
||||
json_skip_whitespace(&line0);
|
||||
|
||||
char *reply_msg = NULL;
|
||||
if (line0[0] == '\0' || line0[0] == '#') {
|
||||
// skip
|
||||
} else if (line0[0] == '{') {
|
||||
reply_msg = json_execute_command(arg, tmp, line0);
|
||||
} else {
|
||||
reply_msg = text_execute_command(arg, tmp, line0);
|
||||
}
|
||||
|
||||
if (reply_msg && arg->writable) {
|
||||
rc = ipc_write_str(arg, reply_msg);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno));
|
||||
talloc_free(tmp);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (client_msg.len > 0)
|
||||
MP_WARN(arg, "Ignoring unterminated command on disconnect.\n");
|
||||
talloc_free(client_msg.start);
|
||||
if (arg->close_client_fd)
|
||||
close(arg->client_fd);
|
||||
mpv_detach_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
|
||||
{
|
||||
client->client = mp_new_client(ctx->client_api, client->client_name),
|
||||
client->log = mp_client_get_log(client->client);
|
||||
|
||||
pthread_t client_thr;
|
||||
if (pthread_create(&client_thr, NULL, client_thread, client)) {
|
||||
mpv_detach_destroy(client->client);
|
||||
if (client->close_client_fd)
|
||||
close(client->client_fd);
|
||||
talloc_free(client);
|
||||
}
|
||||
}
|
||||
|
||||
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
|
||||
{
|
||||
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||
*client = (struct client_arg){
|
||||
.client_name = talloc_asprintf(client, "ipc-%d", id),
|
||||
.client_fd = fd,
|
||||
.close_client_fd = true,
|
||||
|
||||
.writable = true,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
}
|
||||
|
||||
static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
||||
{
|
||||
int mode = O_RDONLY;
|
||||
int client_fd = -1;
|
||||
bool close_client_fd = true;
|
||||
bool writable = false;
|
||||
|
||||
if (strcmp(path, "/dev/stdin") == 0) { // for symmetry with Linux
|
||||
client_fd = STDIN_FILENO;
|
||||
close_client_fd = false;
|
||||
} else if (strncmp(path, "fd://", 5) == 0) {
|
||||
char *end = NULL;
|
||||
client_fd = strtol(path + 5, &end, 0);
|
||||
if (!end || end == path + 5 || end[0]) {
|
||||
MP_ERR(ctx, "Invalid FD: %s\n", path);
|
||||
return;
|
||||
}
|
||||
close_client_fd = false;
|
||||
writable = true; // maybe
|
||||
char *reply_msg = NULL;
|
||||
if (line0[0] == '\0' || line0[0] == '#') {
|
||||
// skip
|
||||
} else if (line0[0] == '{') {
|
||||
reply_msg = json_execute_command(client, tmp, line0);
|
||||
} else {
|
||||
// Use RDWR for FIFOs to ensure they stay open over multiple accesses.
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0 && S_ISFIFO(st.st_mode))
|
||||
mode = O_RDWR;
|
||||
client_fd = open(path, mode);
|
||||
}
|
||||
if (client_fd < 0) {
|
||||
MP_ERR(ctx, "Could not open '%s'\n", path);
|
||||
return;
|
||||
reply_msg = text_execute_command(client, tmp, line0);
|
||||
}
|
||||
|
||||
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||
*client = (struct client_arg){
|
||||
.client_name = "input-file",
|
||||
.client_fd = client_fd,
|
||||
.close_client_fd = close_client_fd,
|
||||
.writable = writable,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
}
|
||||
|
||||
static void *ipc_thread(void *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
int ipc_fd;
|
||||
struct sockaddr_un ipc_un = {0};
|
||||
|
||||
struct mp_ipc_ctx *arg = p;
|
||||
|
||||
mpthread_set_name("ipc socket listener");
|
||||
|
||||
MP_VERBOSE(arg, "Starting IPC master\n");
|
||||
|
||||
ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ipc_fd < 0) {
|
||||
MP_ERR(arg, "Could not create IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if HAVE_FCHMOD
|
||||
fchmod(ipc_fd, 0600);
|
||||
#endif
|
||||
|
||||
size_t path_len = strlen(arg->path);
|
||||
if (path_len >= sizeof(ipc_un.sun_path) - 1) {
|
||||
MP_ERR(arg, "Could not create IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_un.sun_family = AF_UNIX,
|
||||
strncpy(ipc_un.sun_path, arg->path, sizeof(ipc_un.sun_path));
|
||||
|
||||
unlink(ipc_un.sun_path);
|
||||
|
||||
if (ipc_un.sun_path[0] == '@') {
|
||||
ipc_un.sun_path[0] = '\0';
|
||||
path_len--;
|
||||
}
|
||||
|
||||
size_t addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + path_len;
|
||||
rc = bind(ipc_fd, (struct sockaddr *) &ipc_un, addr_len);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Could not bind IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = listen(ipc_fd, 10);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Could not listen on IPC socket\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
int client_num = 0;
|
||||
|
||||
struct pollfd fds[2] = {
|
||||
{.events = POLLIN, .fd = arg->death_pipe[0]},
|
||||
{.events = POLLIN, .fd = ipc_fd},
|
||||
};
|
||||
|
||||
while (1) {
|
||||
rc = poll(fds, 2, -1);
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Poll error\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
goto done;
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
int client_fd = accept(ipc_fd, NULL, NULL);
|
||||
if (client_fd < 0) {
|
||||
MP_ERR(arg, "Could not accept IPC client\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ipc_start_client_json(arg, client_num++, client_fd);
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (ipc_fd >= 0)
|
||||
close(ipc_fd);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
|
||||
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
|
||||
*arg = (struct mp_ipc_ctx){
|
||||
.log = mp_log_new(arg, global->log, "ipc"),
|
||||
.client_api = client_api,
|
||||
.path = mp_get_user_path(arg, global, opts->ipc_path),
|
||||
.death_pipe = {-1, -1},
|
||||
};
|
||||
char *input_file = mp_get_user_path(arg, global, opts->input_file);
|
||||
|
||||
if (input_file && *input_file)
|
||||
ipc_start_client_text(arg, input_file);
|
||||
|
||||
if (!opts->ipc_path || !*opts->ipc_path)
|
||||
goto out;
|
||||
|
||||
if (mp_make_wakeup_pipe(arg->death_pipe) < 0)
|
||||
goto out;
|
||||
|
||||
if (pthread_create(&arg->thread, NULL, ipc_thread, arg))
|
||||
goto out;
|
||||
|
||||
return arg;
|
||||
|
||||
out:
|
||||
close(arg->death_pipe[0]);
|
||||
close(arg->death_pipe[1]);
|
||||
talloc_free(arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_uninit_ipc(struct mp_ipc_ctx *arg)
|
||||
{
|
||||
if (!arg)
|
||||
return;
|
||||
|
||||
write(arg->death_pipe[1], &(char){0}, 1);
|
||||
pthread_join(arg->thread, NULL);
|
||||
|
||||
close(arg->death_pipe[0]);
|
||||
close(arg->death_pipe[1]);
|
||||
talloc_free(arg);
|
||||
talloc_steal(ctx, reply_msg);
|
||||
talloc_free(tmp);
|
||||
return reply_msg;
|
||||
}
|
||||
+2
-1
@@ -63,7 +63,8 @@ extern "C" {
|
||||
* * http://mpv.io/manual/master/#list-of-input-commands
|
||||
* * http://mpv.io/manual/master/#properties
|
||||
*
|
||||
* You can also look at the examples in DOCS/client_api_examples/.
|
||||
* You can also look at the examples here:
|
||||
* * https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
*
|
||||
* Event loop
|
||||
* ----------
|
||||
|
||||
+2
-2
@@ -202,9 +202,9 @@ private:
|
||||
if (list->values)
|
||||
free_node(&list->values[n]);
|
||||
}
|
||||
delete[] list->keys;
|
||||
delete[] list->values;
|
||||
}
|
||||
delete[] list->keys;
|
||||
delete[] list->values;
|
||||
delete list;
|
||||
break;
|
||||
}
|
||||
|
||||
+9
-3
@@ -401,15 +401,21 @@ void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt,
|
||||
int size;
|
||||
va_list copy;
|
||||
va_copy(copy, ap);
|
||||
size_t avail = talloc_get_size(s->start) - s->len;
|
||||
char *dest = s->start ? s->start + s->len : NULL;
|
||||
char c;
|
||||
size = vsnprintf(&c, 1, fmt, copy);
|
||||
if (avail < 1)
|
||||
dest = &c;
|
||||
size = vsnprintf(dest, MPMAX(avail, 1), fmt, copy);
|
||||
va_end(copy);
|
||||
|
||||
if (size < 0)
|
||||
abort();
|
||||
|
||||
resize_append(talloc_ctx, s, size + 1);
|
||||
vsnprintf(s->start + s->len, size + 1, fmt, ap);
|
||||
if (avail < 1 || size + 1 > avail) {
|
||||
resize_append(talloc_ctx, s, size + 1);
|
||||
vsnprintf(s->start + s->len, size + 1, fmt, ap);
|
||||
}
|
||||
s->len += size;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -88,7 +88,9 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"vaapi-copy", HWDEC_VAAPI_COPY},
|
||||
{"dxva2", HWDEC_DXVA2},
|
||||
{"dxva2-copy", HWDEC_DXVA2_COPY},
|
||||
{"d3d11va-copy",HWDEC_D3D11VA_COPY},
|
||||
{"rpi", HWDEC_RPI},
|
||||
{"mediacodec", HWDEC_MEDIACODEC},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -581,7 +583,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLAG("input-terminal", consolecontrols, CONF_GLOBAL),
|
||||
|
||||
OPT_STRING("input-file", input_file, M_OPT_FILE | M_OPT_GLOBAL),
|
||||
OPT_STRING("input-unix-socket", ipc_path, M_OPT_FILE),
|
||||
OPT_STRING("input-ipc-server", ipc_path, M_OPT_FILE),
|
||||
|
||||
OPT_SUBSTRUCT("screenshot", screenshot_image_opts, image_writer_conf, 0),
|
||||
OPT_STRING("screenshot-template", screenshot_template, 0),
|
||||
@@ -676,6 +678,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("dtshd", "ad-spdif-dtshd"),
|
||||
OPT_REPLACED("ass-use-margins", "sub-use-margins"),
|
||||
OPT_REPLACED("media-title", "force-media-title"),
|
||||
OPT_REPLACED("input-unix-socket", "input-ipc-server"),
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -706,7 +709,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.border = 1,
|
||||
.WinID = -1,
|
||||
.window_scale = 1.0,
|
||||
.x11_bypass_compositor = 1,
|
||||
.x11_bypass_compositor = 0,
|
||||
.mmcss_profile = "Playback",
|
||||
},
|
||||
.allow_win_drag = 1,
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
|
||||
#include <winver.h>
|
||||
#include "version.h"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 2,0,0,0
|
||||
@@ -26,26 +25,6 @@ PRODUCTVERSION 2,0,0,0
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "000004b0" // LANG_NEUTRAL,UNICODE_CP
|
||||
{
|
||||
VALUE "Comments","mpv is distributed under the terms of the GNU General Public License Version 2 or later.\000"
|
||||
VALUE "CompanyName", "mpv\000"
|
||||
VALUE "FileDescription", "mpv\000"
|
||||
VALUE "FileVersion",VERSION
|
||||
VALUE "LegalCopyright", " (C) 2000-2016 mpv/mplayer2/MPlayer\000"
|
||||
//VALUE "LegalTrademarks"," \000";
|
||||
VALUE "OriginalFilename", "mpv.exe\000"
|
||||
VALUE "ProductName", "mpv\000"
|
||||
VALUE "ProductVersion",VERSION
|
||||
//VALUE "SpecialBuild","\000"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation",0,0x04b0
|
||||
}
|
||||
}
|
||||
|
||||
IDI_ICON1 ICON DISCARDABLE "etc/mpv-icon.ico"
|
||||
|
||||
@@ -134,6 +134,8 @@ static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
|
||||
mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@@ -368,6 +370,8 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
|
||||
update_playback_speed(mpctx);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
|
||||
return;
|
||||
|
||||
init_error:
|
||||
|
||||
+138
-45
@@ -32,6 +32,8 @@
|
||||
#include "config.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "client.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
#include "command.h"
|
||||
@@ -417,6 +419,17 @@ static int mp_property_stream_path(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, stream->url);
|
||||
}
|
||||
|
||||
struct change_stream_capture_args {
|
||||
char *filename;
|
||||
struct demuxer *demux;
|
||||
};
|
||||
|
||||
static void do_change_stream_capture(void *p)
|
||||
{
|
||||
struct change_stream_capture_args *args = p;
|
||||
stream_set_capture_file(args->demux->stream, args->filename);
|
||||
}
|
||||
|
||||
static int mp_property_stream_capture(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -425,10 +438,8 @@ static int mp_property_stream_capture(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_SET) {
|
||||
char *filename = *(char **)arg;
|
||||
demux_pause(mpctx->demuxer);
|
||||
stream_set_capture_file(mpctx->demuxer->stream, filename);
|
||||
demux_unpause(mpctx->demuxer);
|
||||
struct change_stream_capture_args args = {*(char **)arg, mpctx->demuxer};
|
||||
demux_run_on_thread(mpctx->demuxer, do_change_stream_capture, &args);
|
||||
// fall through to mp_property_generic_option
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
@@ -456,24 +467,14 @@ static int mp_property_file_format(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, name);
|
||||
}
|
||||
|
||||
/// Position in the stream (RW)
|
||||
static int mp_property_stream_pos(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct demuxer *demuxer = mpctx->demuxer;
|
||||
if (!demuxer)
|
||||
if (!demuxer || demuxer->filepos < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
demux_pause(demuxer);
|
||||
int r;
|
||||
if (action == M_PROPERTY_SET) {
|
||||
stream_seek(demuxer->stream, *(int64_t *) arg);
|
||||
r = M_PROPERTY_OK;
|
||||
} else {
|
||||
r = m_property_int64_ro(action, arg, stream_tell(demuxer->stream));
|
||||
}
|
||||
demux_unpause(demuxer);
|
||||
return r;
|
||||
return m_property_int64_ro(action, arg, demuxer->filepos);
|
||||
}
|
||||
|
||||
/// Stream end offset (RO)
|
||||
@@ -1085,11 +1086,12 @@ static int mp_property_angle(void *ctx, struct m_property *prop,
|
||||
if (angle < 0 || angle > angles)
|
||||
return M_PROPERTY_ERROR;
|
||||
|
||||
demux_pause(demuxer);
|
||||
demux_flush(demuxer);
|
||||
ris = demux_stream_control(demuxer, STREAM_CTRL_SET_ANGLE, &angle);
|
||||
demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL);
|
||||
demux_unpause(demuxer);
|
||||
if (ris == STREAM_OK) {
|
||||
demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL);
|
||||
demux_flush(demuxer);
|
||||
}
|
||||
|
||||
reset_audio_state(mpctx);
|
||||
reset_video_state(mpctx);
|
||||
@@ -1368,11 +1370,11 @@ static int mp_property_cache_size(void *ctx, struct m_property *prop,
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
case M_PROPERTY_PRINT: {
|
||||
int64_t size = -1;
|
||||
demux_stream_control(demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size);
|
||||
if (size <= 0)
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size <= 0)
|
||||
break;
|
||||
return property_int_kb_size(size / 1024, action, arg);
|
||||
return property_int_kb_size(info.size / 1024, action, arg);
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
@@ -1401,11 +1403,11 @@ static int mp_property_cache_used(void *ctx, struct m_property *prop,
|
||||
if (!mpctx->demuxer)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
int64_t size = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &size);
|
||||
if (size < 0)
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return property_int_kb_size(size / 1024, action, arg);
|
||||
return property_int_kb_size(info.fill / 1024, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_cache_free(void *ctx, struct m_property *prop,
|
||||
@@ -1415,29 +1417,43 @@ static int mp_property_cache_free(void *ctx, struct m_property *prop,
|
||||
if (!mpctx->demuxer)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
int64_t size_used = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &size_used);
|
||||
if (size_used < 0)
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
int64_t size = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size);
|
||||
if (size <= 0)
|
||||
return property_int_kb_size((info.size - info.fill) / 1024, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_cache_speed(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->demuxer)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
return property_int_kb_size((size - size_used) / 1024, action, arg);
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_PRINT) {
|
||||
*(char **)arg = talloc_strdup_append(format_file_size(info.speed), "/s");
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return m_property_int64_ro(action, arg, info.speed);
|
||||
}
|
||||
|
||||
static int mp_property_cache_idle(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
int idle = -1;
|
||||
struct stream_cache_info info = {0};
|
||||
if (mpctx->demuxer)
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle);
|
||||
if (idle < 0)
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_flag_ro(action, arg, !!idle);
|
||||
return m_property_flag_ro(action, arg, info.idle);
|
||||
}
|
||||
|
||||
static int mp_property_demuxer_cache_duration(void *ctx, struct m_property *prop,
|
||||
@@ -1944,7 +1960,14 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
|
||||
struct MPContext *mpctx = ctx;
|
||||
struct track *track = mpctx->tracks[item];
|
||||
|
||||
const char *codec = track->stream ? track->stream->codec->codec : NULL;
|
||||
struct mp_codec_params p =
|
||||
track->stream ? *track->stream->codec : (struct mp_codec_params){0};
|
||||
|
||||
const char *decoder_desc = NULL;
|
||||
if (track->d_video)
|
||||
decoder_desc = track->d_video->decoder_desc;
|
||||
if (track->d_audio)
|
||||
decoder_desc = track->d_audio->decoder_desc;
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
{"id", SUB_PROP_INT(track->user_tid)},
|
||||
@@ -1965,9 +1988,20 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
|
||||
{"selected", SUB_PROP_FLAG(track->selected)},
|
||||
{"external-filename", SUB_PROP_STR(track->external_filename),
|
||||
.unavailable = !track->external_filename},
|
||||
{"codec", SUB_PROP_STR(codec),
|
||||
.unavailable = !codec},
|
||||
{"ff-index", SUB_PROP_INT(track->ff_index)},
|
||||
{"decoder-desc", SUB_PROP_STR(decoder_desc),
|
||||
.unavailable = !decoder_desc},
|
||||
{"codec", SUB_PROP_STR(p.codec),
|
||||
.unavailable = !p.codec},
|
||||
{"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w},
|
||||
{"demux-h", SUB_PROP_INT(p.disp_h), .unavailable = !p.disp_h},
|
||||
{"demux-channel-count", SUB_PROP_INT(p.channels.num),
|
||||
.unavailable = !p.channels.num},
|
||||
{"demux-channels", SUB_PROP_STR(mp_chmap_to_str(&p.channels)),
|
||||
.unavailable = !p.channels.num},
|
||||
{"demux-samplerate", SUB_PROP_INT(p.samplerate),
|
||||
.unavailable = !p.samplerate},
|
||||
{"demux-fps", SUB_PROP_DOUBLE(p.fps), .unavailable = p.fps <= 0},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -2699,7 +2733,7 @@ static int mp_property_osd_w(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
|
||||
return m_property_int_ro(action, arg, vo_res.w);
|
||||
}
|
||||
|
||||
@@ -2707,7 +2741,7 @@ static int mp_property_osd_h(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
|
||||
return m_property_int_ro(action, arg, vo_res.h);
|
||||
}
|
||||
|
||||
@@ -2715,7 +2749,7 @@ static int mp_property_osd_par(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
|
||||
return m_property_double_ro(action, arg, vo_res.display_par);
|
||||
}
|
||||
|
||||
@@ -3272,6 +3306,47 @@ static int mp_property_protocols(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int get_decoder_entry(int item, int action, void *arg, void *ctx)
|
||||
{
|
||||
struct mp_decoder_list *codecs = ctx;
|
||||
struct mp_decoder_entry *c = &codecs->entries[item];
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
{"family", SUB_PROP_STR(c->family)},
|
||||
{"codec", SUB_PROP_STR(c->codec)},
|
||||
{"driver" , SUB_PROP_STR(c->decoder)},
|
||||
{"description", SUB_PROP_STR(c->desc)},
|
||||
{0}
|
||||
};
|
||||
|
||||
return m_property_read_sub(props, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_decoders(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list);
|
||||
struct mp_decoder_list *v = talloc_steal(codecs, video_decoder_list());
|
||||
struct mp_decoder_list *a = talloc_steal(codecs, audio_decoder_list());
|
||||
mp_append_decoders(codecs, v);
|
||||
mp_append_decoders(codecs, a);
|
||||
int r = m_property_read_list(action, arg, codecs->num_entries,
|
||||
get_decoder_entry, codecs);
|
||||
talloc_free(codecs);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mp_property_encoders(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list);
|
||||
mp_add_lavc_encoders(codecs);
|
||||
int r = m_property_read_list(action, arg, codecs->num_entries,
|
||||
get_decoder_entry, codecs);
|
||||
talloc_free(codecs);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mp_property_version(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -3284,6 +3359,16 @@ static int mp_property_configuration(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, CONFIGURATION);
|
||||
}
|
||||
|
||||
static int mp_property_ffmpeg(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
#if HAVE_AV_VERSION_INFO
|
||||
return m_property_strdup_ro(action, arg, av_version_info());
|
||||
#else
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int mp_property_alias(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -3535,6 +3620,7 @@ static const struct m_property mp_properties[] = {
|
||||
{"cache-used", mp_property_cache_used},
|
||||
{"cache-size", mp_property_cache_size},
|
||||
{"cache-idle", mp_property_cache_idle},
|
||||
{"cache-speed", mp_property_cache_speed},
|
||||
{"demuxer-cache-duration", mp_property_demuxer_cache_duration},
|
||||
{"demuxer-cache-time", mp_property_demuxer_cache_time},
|
||||
{"demuxer-cache-idle", mp_property_demuxer_cache_idle},
|
||||
@@ -3591,6 +3677,7 @@ static const struct m_property mp_properties[] = {
|
||||
{"video-output-levels", mp_property_video_color,
|
||||
.priv = (void *)"output-levels"},
|
||||
{"panscan", panscan_property_helper},
|
||||
{"keepaspect", panscan_property_helper},
|
||||
{"video-zoom", panscan_property_helper},
|
||||
{"video-align-x", panscan_property_helper},
|
||||
{"video-align-y", panscan_property_helper},
|
||||
@@ -3645,6 +3732,7 @@ static const struct m_property mp_properties[] = {
|
||||
{"af", mp_property_af},
|
||||
|
||||
{"video-rotate", video_simple_refresh_property},
|
||||
{"video-stereo-mode", video_simple_refresh_property},
|
||||
|
||||
{"ab-loop-a", mp_property_ab_loop},
|
||||
{"ab-loop-b", mp_property_ab_loop},
|
||||
@@ -3689,9 +3777,12 @@ static const struct m_property mp_properties[] = {
|
||||
{"working-directory", mp_property_cwd},
|
||||
|
||||
{"protocol-list", mp_property_protocols},
|
||||
{"decoder-list", mp_property_decoders},
|
||||
{"encoder-list", mp_property_encoders},
|
||||
|
||||
{"mpv-version", mp_property_version},
|
||||
{"mpv-configuration", mp_property_configuration},
|
||||
{"ffmpeg-version", mp_property_ffmpeg},
|
||||
|
||||
{"options", mp_property_options},
|
||||
{"file-local-options", mp_property_local_options},
|
||||
@@ -3746,7 +3837,7 @@ static const char *const *const mp_event_property_change[] = {
|
||||
E(MPV_EVENT_CHAPTER_CHANGE, "chapter", "chapter-metadata"),
|
||||
E(MP_EVENT_CACHE_UPDATE, "cache", "cache-free", "cache-used", "cache-idle",
|
||||
"demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache",
|
||||
"demuxer-cache-time"),
|
||||
"demuxer-cache-time", "cache-buffering-state", "cache-speed"),
|
||||
E(MP_EVENT_WIN_RESIZE, "window-scale", "osd-width", "osd-height", "osd-par"),
|
||||
E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps", "fullscreen"),
|
||||
};
|
||||
@@ -4753,6 +4844,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s);
|
||||
if (t) {
|
||||
mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION);
|
||||
if (mpctx->playback_initialized)
|
||||
print_track_list(mpctx, "Track switched:");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,10 +147,6 @@ struct track {
|
||||
struct vo_chain *vo_c;
|
||||
struct ao_chain *ao_c;
|
||||
struct lavfi_pad *sink;
|
||||
|
||||
// For external subtitles, which are read fully on init. Do not attempt
|
||||
// to read packets from them.
|
||||
bool preloaded;
|
||||
};
|
||||
|
||||
// Summarizes video filtering and output.
|
||||
|
||||
@@ -20,6 +20,7 @@ static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "sr
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
"ogg", "m4a", "ac3", "opus", "wav",
|
||||
"wv",
|
||||
NULL};
|
||||
|
||||
static bool test_ext_list(bstr ext, const char *const *list)
|
||||
|
||||
+1
-1
@@ -1027,7 +1027,7 @@ reopen_file:
|
||||
goto terminate_playback;
|
||||
|
||||
if (opts->stream_dump && opts->stream_dump[0]) {
|
||||
if (stream_dump(mpctx, mpctx->stream_open_filename) < 0)
|
||||
if (stream_dump(mpctx, mpctx->stream_open_filename) >= 0)
|
||||
mpctx->error_playing = 1;
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
+16
-31
@@ -391,6 +391,7 @@ static int load_lua(struct mpv_handle *client, const char *fname)
|
||||
r = 0;
|
||||
|
||||
error_out:
|
||||
osd_set_external(ctx->mpctx->osd, client, 0, 0, NULL); // remove overlay
|
||||
mp_resume_all(client);
|
||||
if (ctx->state)
|
||||
lua_close(ctx->state);
|
||||
@@ -963,31 +964,21 @@ static int script_command_native(lua_State *L)
|
||||
|
||||
static int script_set_osd_ass(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
struct script_ctx *ctx = get_ctx(L);
|
||||
int res_x = luaL_checkinteger(L, 1);
|
||||
int res_y = luaL_checkinteger(L, 2);
|
||||
const char *text = luaL_checkstring(L, 3);
|
||||
if (!text[0])
|
||||
text = " "; // force external OSD initialization
|
||||
osd_set_external(mpctx->osd, res_x, res_y, (char *)text);
|
||||
mp_input_wakeup(mpctx->input);
|
||||
osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text);
|
||||
mp_input_wakeup(ctx->mpctx->input);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int script_get_osd_resolution(lua_State *L)
|
||||
static int script_get_osd_size(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
int w, h;
|
||||
osd_object_get_resolution(mpctx->osd, OSDTYPE_EXTERNAL, &w, &h);
|
||||
lua_pushnumber(L, w);
|
||||
lua_pushnumber(L, h);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int script_get_screen_size(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_EXTERNAL);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
|
||||
double aspect = 1.0 * vo_res.w / MPMAX(vo_res.h, 1) /
|
||||
(vo_res.display_par ? vo_res.display_par : 1);
|
||||
lua_pushnumber(L, vo_res.w);
|
||||
@@ -996,10 +987,10 @@ static int script_get_screen_size(lua_State *L)
|
||||
return 3;
|
||||
}
|
||||
|
||||
static int script_get_screen_margins(lua_State *L)
|
||||
static int script_get_osd_margins(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_EXTERNAL);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
|
||||
lua_pushnumber(L, vo_res.ml);
|
||||
lua_pushnumber(L, vo_res.mt);
|
||||
lua_pushnumber(L, vo_res.mr);
|
||||
@@ -1012,10 +1003,8 @@ static int script_get_mouse_pos(lua_State *L)
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
int px, py;
|
||||
mp_input_get_mouse_pos(mpctx->input, &px, &py);
|
||||
double sw, sh;
|
||||
osd_object_get_scale_factor(mpctx->osd, OSDTYPE_EXTERNAL, &sw, &sh);
|
||||
lua_pushnumber(L, px * sw);
|
||||
lua_pushnumber(L, py * sh);
|
||||
lua_pushnumber(L, px);
|
||||
lua_pushnumber(L, py);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -1030,14 +1019,11 @@ static int script_input_set_section_mouse_area(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
|
||||
double sw, sh;
|
||||
osd_object_get_scale_factor(mpctx->osd, OSDTYPE_EXTERNAL, &sw, &sh);
|
||||
|
||||
char *section = (char *)luaL_checkstring(L, 1);
|
||||
int x0 = sw ? luaL_checkinteger(L, 2) / sw : 0;
|
||||
int y0 = sh ? luaL_checkinteger(L, 3) / sh : 0;
|
||||
int x1 = sw ? luaL_checkinteger(L, 4) / sw : 0;
|
||||
int y1 = sh ? luaL_checkinteger(L, 5) / sh : 0;
|
||||
int x0 = luaL_checkinteger(L, 2);
|
||||
int y0 = luaL_checkinteger(L, 3);
|
||||
int x1 = luaL_checkinteger(L, 4);
|
||||
int y1 = luaL_checkinteger(L, 5);
|
||||
mp_input_set_section_mouse_area(mpctx->input, section, x0, y0, x1, y1);
|
||||
return 0;
|
||||
}
|
||||
@@ -1266,9 +1252,8 @@ static const struct fn_entry main_fns[] = {
|
||||
FN_ENTRY(raw_observe_property),
|
||||
FN_ENTRY(raw_unobserve_property),
|
||||
FN_ENTRY(set_osd_ass),
|
||||
FN_ENTRY(get_osd_resolution),
|
||||
FN_ENTRY(get_screen_size),
|
||||
FN_ENTRY(get_screen_margins),
|
||||
FN_ENTRY(get_osd_size),
|
||||
FN_ENTRY(get_osd_margins),
|
||||
FN_ENTRY(get_mouse_pos),
|
||||
FN_ENTRY(get_time),
|
||||
FN_ENTRY(input_set_section_mouse_area),
|
||||
|
||||
@@ -145,7 +145,7 @@ local function update_key_bindings()
|
||||
end
|
||||
local cfg = ""
|
||||
for k, v in pairs(key_bindings) do
|
||||
if v.forced ~= def then
|
||||
if v.bind and v.forced ~= def then
|
||||
cfg = cfg .. v.bind .. "\n"
|
||||
end
|
||||
end
|
||||
@@ -161,7 +161,6 @@ local function add_binding(attrs, key, name, fn, rp)
|
||||
fn = name
|
||||
name = reserve_binding()
|
||||
end
|
||||
local bind = key
|
||||
local repeatable = rp == "repeatable" or rp["repeatable"]
|
||||
if rp["forced"] then
|
||||
attrs.forced = true
|
||||
@@ -205,7 +204,9 @@ local function add_binding(attrs, key, name, fn, rp)
|
||||
end
|
||||
msg_cb = fn
|
||||
end
|
||||
attrs.bind = bind .. " script-binding " .. mp.script_name .. "/" .. name
|
||||
if key and #key > 0 then
|
||||
attrs.bind = key .. " script-binding " .. mp.script_name .. "/" .. name
|
||||
end
|
||||
attrs.name = name
|
||||
key_bindings[name] = attrs
|
||||
update_key_bindings()
|
||||
@@ -438,7 +439,7 @@ local function call_event_handlers(e)
|
||||
end
|
||||
end
|
||||
|
||||
mp.use_suspend = true
|
||||
mp.use_suspend = false
|
||||
|
||||
function mp.dispatch_events(allow_wait)
|
||||
local more_events = true
|
||||
|
||||
+30
-9
@@ -99,6 +99,27 @@ local state = {
|
||||
-- Helperfunctions
|
||||
--
|
||||
|
||||
-- scale factor for translating between real and virtual ASS coordinates
|
||||
function get_virt_scale_factor()
|
||||
local w, h = mp.get_osd_size()
|
||||
if w <= 0 or h <= 0 then
|
||||
return 0, 0
|
||||
end
|
||||
return osc_param.playresx / w, osc_param.playresy / h
|
||||
end
|
||||
|
||||
-- return mouse position in virtual ASS coordinates (playresx/y)
|
||||
function get_virt_mouse_pos()
|
||||
local sx, sy = get_virt_scale_factor()
|
||||
local x, y = mp.get_mouse_pos()
|
||||
return x * sx, y * sy
|
||||
end
|
||||
|
||||
function set_virt_mouse_area(x0, y0, x1, y1, name)
|
||||
local sx, sy = get_virt_scale_factor()
|
||||
mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name)
|
||||
end
|
||||
|
||||
function scale_value(x0, x1, y0, y1, val)
|
||||
local m = (y1 - y0) / (x1 - x0)
|
||||
local b = y0 - (m * x0)
|
||||
@@ -141,7 +162,7 @@ function mouse_hit(element)
|
||||
end
|
||||
|
||||
function mouse_hit_coords(bX1, bY1, bX2, bY2)
|
||||
local mX, mY = mp.get_mouse_pos()
|
||||
local mX, mY = get_virt_mouse_pos()
|
||||
return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
|
||||
end
|
||||
|
||||
@@ -182,7 +203,7 @@ end
|
||||
|
||||
-- get value at current mouse position
|
||||
function get_slider_value(element)
|
||||
return get_slider_value_at(element, mp.get_mouse_pos())
|
||||
return get_slider_value_at(element, get_virt_mouse_pos())
|
||||
end
|
||||
|
||||
function countone(val)
|
||||
@@ -589,7 +610,7 @@ function render_elements(master_ass)
|
||||
end
|
||||
|
||||
elem_ass:new_event()
|
||||
elem_ass:pos(mp.get_mouse_pos(), ty)
|
||||
elem_ass:pos(get_virt_mouse_pos(), ty)
|
||||
elem_ass:an(an)
|
||||
elem_ass:append(slider_lo.tooltip_style)
|
||||
elem_ass:append(tooltiplabel)
|
||||
@@ -1298,7 +1319,7 @@ function osc_init()
|
||||
|
||||
-- set canvas resolution according to display aspect and scaling setting
|
||||
local baseResY = 720
|
||||
local display_w, display_h, display_aspect = mp.get_screen_size()
|
||||
local display_w, display_h, display_aspect = mp.get_osd_size()
|
||||
local scale = 1
|
||||
|
||||
if (mp.get_property("video") == "no") then -- dummy/forced window
|
||||
@@ -1718,8 +1739,8 @@ end
|
||||
|
||||
function render()
|
||||
msg.debug("rendering")
|
||||
local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_screen_size()
|
||||
local mouseX, mouseY = mp.get_mouse_pos()
|
||||
local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
|
||||
local mouseX, mouseY = get_virt_mouse_pos()
|
||||
local now = mp.get_time()
|
||||
|
||||
-- check if display changed, if so request reinit
|
||||
@@ -1782,7 +1803,7 @@ function render()
|
||||
|
||||
--mouse show/hide area
|
||||
for k,cords in pairs(osc_param.areas["showhide"]) do
|
||||
mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
|
||||
set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
|
||||
end
|
||||
do_enable_keybindings()
|
||||
|
||||
@@ -1791,7 +1812,7 @@ function render()
|
||||
|
||||
for _,cords in ipairs(osc_param.areas["input"]) do
|
||||
if state.osc_visible then -- activate only when OSC is actually visible
|
||||
mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
|
||||
set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
|
||||
end
|
||||
if state.osc_visible ~= state.input_enabled then
|
||||
if state.osc_visible then
|
||||
@@ -1887,7 +1908,7 @@ function process_event(source, what)
|
||||
state.mouse_down_counter = 0
|
||||
|
||||
elseif source == "mouse_move" then
|
||||
local mouseX, mouseY = mp.get_mouse_pos()
|
||||
local mouseX, mouseY = get_virt_mouse_pos()
|
||||
if (user_opts.minmousemove == 0) or
|
||||
(not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
|
||||
((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove)
|
||||
|
||||
@@ -207,7 +207,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
-- audio url
|
||||
mp.commandv("audio-add", json["requested_formats"][2].url,
|
||||
"select", json["requested_formats"][2]["format_note"])
|
||||
"select", json["requested_formats"][2]["format_note"] or "")
|
||||
|
||||
elseif not (json.url == nil) then
|
||||
-- normal video
|
||||
|
||||
@@ -472,9 +472,7 @@ int mp_initialize(struct MPContext *mpctx, char **options)
|
||||
if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0)
|
||||
return -1;
|
||||
|
||||
#if !defined(__MINGW32__)
|
||||
mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (opts->w32_priority > 0)
|
||||
|
||||
+8
-11
@@ -95,23 +95,20 @@ double get_play_end_pts(struct MPContext *mpctx)
|
||||
|
||||
float mp_get_cache_percent(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->demuxer) {
|
||||
int64_t size = -1;
|
||||
int64_t fill = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size);
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &fill);
|
||||
if (size > 0 && fill >= 0)
|
||||
return fill / (size / 100.0);
|
||||
}
|
||||
struct stream_cache_info info = {0};
|
||||
if (mpctx->demuxer)
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size > 0 && info.fill >= 0)
|
||||
return info.fill / (info.size / 100.0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool mp_get_cache_idle(struct MPContext *mpctx)
|
||||
{
|
||||
int idle = 0;
|
||||
struct stream_cache_info info = {0};
|
||||
if (mpctx->demuxer)
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle);
|
||||
return idle;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
return info.idle;
|
||||
}
|
||||
|
||||
void update_vo_playback_state(struct MPContext *mpctx)
|
||||
|
||||
+7
-7
@@ -257,9 +257,9 @@ static void print_status(struct MPContext *mpctx)
|
||||
}
|
||||
|
||||
if (mpctx->demuxer) {
|
||||
int64_t fill = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &fill);
|
||||
if (fill >= 0) {
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
if (info.size > 0) {
|
||||
saddf(&line, " Cache: ");
|
||||
|
||||
struct demux_ctrl_reader_state s = {.ts_duration = -1};
|
||||
@@ -270,10 +270,10 @@ static void print_status(struct MPContext *mpctx)
|
||||
} else {
|
||||
saddf(&line, "%2ds", (int)s.ts_duration);
|
||||
}
|
||||
if (fill >= 1024 * 1024) {
|
||||
saddf(&line, "+%lldMB", (long long)(fill / 1024 / 1024));
|
||||
if (info.fill >= 1024 * 1024) {
|
||||
saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
|
||||
} else {
|
||||
saddf(&line, "+%lldKB", (long long)(fill / 1024));
|
||||
saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,6 +561,6 @@ void update_osd_msg(struct MPContext *mpctx)
|
||||
text = talloc_asprintf_append(text, "%s%s", text ? "\n" : "",
|
||||
mpctx->osd_msg_text);
|
||||
}
|
||||
osd_set_text(osd, OSDTYPE_OSD, text);
|
||||
osd_set_text(osd, text);
|
||||
talloc_free(text);
|
||||
}
|
||||
+3
-2
@@ -522,8 +522,9 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
|
||||
if (!mpctx->demuxer)
|
||||
return;
|
||||
|
||||
int idle = -1;
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle);
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
int idle = info.size > 0 ? info.idle : -1;
|
||||
|
||||
struct demux_ctrl_reader_state s = {.idle = true, .ts_duration = -1};
|
||||
demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s);
|
||||
|
||||
+31
-12
@@ -71,8 +71,7 @@ void uninit_sub(struct MPContext *mpctx, struct track *track)
|
||||
reset_subtitles(mpctx, track);
|
||||
sub_select(track->d_sub, false);
|
||||
int order = get_order(mpctx, track);
|
||||
if (order >= 0 && order <= 1)
|
||||
osd_set_sub(mpctx->osd, OSDTYPE_SUB + order, NULL);
|
||||
osd_set_sub(mpctx->osd, order, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,18 +98,15 @@ static bool update_subtitle(struct MPContext *mpctx, double video_pts,
|
||||
|
||||
video_pts -= opts->sub_delay;
|
||||
|
||||
if (!track->preloaded && track->demuxer->fully_read && !opts->sub_clear_on_seek)
|
||||
{
|
||||
if (track->demuxer->fully_read && sub_can_preload(dec_sub)) {
|
||||
// Assume fully_read implies no interleaved audio/video streams.
|
||||
// (Reading packets will change the demuxer position.)
|
||||
demux_seek(track->demuxer, 0, 0);
|
||||
track->preloaded = sub_read_all_packets(track->d_sub);
|
||||
sub_preload(dec_sub);
|
||||
}
|
||||
|
||||
if (!track->preloaded) {
|
||||
if (!sub_read_packets(dec_sub, video_pts))
|
||||
return false;
|
||||
}
|
||||
if (!sub_read_packets(dec_sub, video_pts))
|
||||
return false;
|
||||
|
||||
// Handle displaying subtitles on terminal; never done for secondary subs
|
||||
if (mpctx->current_track[0][STREAM_SUB] == track && !mpctx->video_out)
|
||||
@@ -129,6 +125,29 @@ bool update_subtitles(struct MPContext *mpctx, double video_pts)
|
||||
return ok;
|
||||
}
|
||||
|
||||
static struct attachment_list *get_all_attachments(struct MPContext *mpctx)
|
||||
{
|
||||
struct attachment_list *list = talloc_zero(NULL, struct attachment_list);
|
||||
struct demuxer *prev_demuxer = NULL;
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *t = mpctx->tracks[n];
|
||||
if (!t->demuxer || prev_demuxer == t->demuxer)
|
||||
continue;
|
||||
prev_demuxer = t->demuxer;
|
||||
for (int i = 0; i < t->demuxer->num_attachments; i++) {
|
||||
struct demux_attachment *att = &t->demuxer->attachments[i];
|
||||
struct demux_attachment copy = {
|
||||
.name = talloc_strdup(list, att->name),
|
||||
.type = talloc_strdup(list, att->type),
|
||||
.data = talloc_memdup(list, att->data, att->data_size),
|
||||
.data_size = att->data_size,
|
||||
};
|
||||
MP_TARRAY_APPEND(list, list->entries, list->num_entries, copy);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static bool init_subdec(struct MPContext *mpctx, struct track *track)
|
||||
{
|
||||
assert(!track->d_sub);
|
||||
@@ -136,7 +155,8 @@ static bool init_subdec(struct MPContext *mpctx, struct track *track)
|
||||
if (!track->demuxer || !track->stream)
|
||||
return false;
|
||||
|
||||
track->d_sub = sub_create(mpctx->global, track->demuxer, track->stream);
|
||||
track->d_sub = sub_create(mpctx->global, track->stream,
|
||||
get_all_attachments(mpctx));
|
||||
if (!track->d_sub)
|
||||
return false;
|
||||
|
||||
@@ -161,8 +181,7 @@ void reinit_sub(struct MPContext *mpctx, struct track *track)
|
||||
|
||||
sub_select(track->d_sub, true);
|
||||
int order = get_order(mpctx, track);
|
||||
if (order >= 0 && order <= 1)
|
||||
osd_set_sub(mpctx->osd, OSDTYPE_SUB + order, track->d_sub);
|
||||
osd_set_sub(mpctx->osd, order, track->d_sub);
|
||||
sub_control(track->d_sub, SD_CTRL_SET_TOP, &(bool){!!order});
|
||||
}
|
||||
|
||||
|
||||
+15
-9
@@ -136,8 +136,7 @@ static void set_allowed_vo_formats(struct vo_chain *vo_c)
|
||||
vo_query_formats(vo_c->vo, vo_c->vf->allowed_output_formats);
|
||||
}
|
||||
|
||||
static int try_filter(struct vo_chain *vo_c, struct mp_image_params params,
|
||||
char *name, char *label, char **args)
|
||||
static int try_filter(struct vo_chain *vo_c, char *name, char *label, char **args)
|
||||
{
|
||||
struct vf_instance *vf = vf_append_filter(vo_c->vf, name, args);
|
||||
if (!vf)
|
||||
@@ -145,10 +144,10 @@ static int try_filter(struct vo_chain *vo_c, struct mp_image_params params,
|
||||
|
||||
vf->label = talloc_strdup(vf, label);
|
||||
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0) {
|
||||
if (vf_reconfig(vo_c->vf, &vo_c->input_format) < 0) {
|
||||
vf_remove_filter(vo_c->vf, vf);
|
||||
// restore
|
||||
vf_reconfig(vo_c->vf, ¶ms);
|
||||
vf_reconfig(vo_c->vf, &vo_c->input_format);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -166,15 +165,22 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
|
||||
char *filters[] = {"autorotate", "autostereo3d", NULL};
|
||||
for (int n = 0; filters[n]; n++) {
|
||||
struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]);
|
||||
if (vf) {
|
||||
vf_remove_filter(vo_c->vf, vf);
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.rotate && (params.rotate % 90 == 0)) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) {
|
||||
// Try to insert a rotation filter.
|
||||
char *args[] = {"angle", "auto", NULL};
|
||||
if (try_filter(vo_c, params, "rotate", "autorotate", args) >= 0) {
|
||||
params.rotate = 0;
|
||||
} else {
|
||||
if (try_filter(vo_c, "rotate", "autorotate", args) < 0)
|
||||
MP_ERR(vo_c, "Can't insert rotation filter.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +190,7 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
char *to = (char *)MP_STEREO3D_NAME(params.stereo_out);
|
||||
if (to) {
|
||||
char *args[] = {"in", "auto", "out", to, NULL, NULL};
|
||||
if (try_filter(vo_c, params, "stereo3d", "stereo3d", args) < 0)
|
||||
if (try_filter(vo_c, "stereo3d", "autostereo3d", args) < 0)
|
||||
MP_ERR(vo_c, "Can't insert 3D conversion filter.\n");
|
||||
}
|
||||
}
|
||||
|
||||
+61
-36
@@ -42,6 +42,7 @@
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
@@ -95,6 +96,9 @@ struct priv {
|
||||
|
||||
bool idle; // cache thread has stopped reading
|
||||
int64_t reads; // number of actual read attempts performed
|
||||
int64_t speed_start; // start time (us) for calculating download speed
|
||||
int64_t speed_amount; // bytes read since speed_start
|
||||
double speed;
|
||||
|
||||
bool enable_readahead; // actively read beyond read() position
|
||||
int64_t read_filepos; // client read position (mirrors cache->pos)
|
||||
@@ -132,7 +136,7 @@ static void cache_wakeup_and_wait(struct priv *s, double *retry_time)
|
||||
{
|
||||
double start = mp_time_sec();
|
||||
if (*retry_time >= CACHE_WAIT_TIME) {
|
||||
MP_WARN(s, "Cache is not responding - slow/stuck network connection?\n");
|
||||
MP_VERBOSE(s, "Cache is not responding - slow/stuck network connection?\n");
|
||||
*retry_time = -1; // do not warn again for this call
|
||||
}
|
||||
|
||||
@@ -152,6 +156,16 @@ static void cache_drop_contents(struct priv *s)
|
||||
s->start_pts = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void update_speed(struct priv *s)
|
||||
{
|
||||
int64_t now = mp_time_us();
|
||||
s->speed = 0;
|
||||
if (s->speed_start && s->speed_start < now)
|
||||
s->speed = s->speed_amount * 1e6 / (now - s->speed_start);
|
||||
s->speed_amount = 0;
|
||||
s->speed_start = now;
|
||||
}
|
||||
|
||||
// Copy at most dst_size from the cache at the given absolute file position pos.
|
||||
// Return number of bytes that could actually be read.
|
||||
// Does not advance the file position, or change anything else.
|
||||
@@ -190,6 +204,7 @@ static size_t read_buffer(struct priv *s, unsigned char *dst,
|
||||
static void cache_fill(struct priv *s)
|
||||
{
|
||||
int64_t read = s->read_filepos;
|
||||
bool read_attempted = false;
|
||||
int len = 0;
|
||||
|
||||
// drop cache contents only if seeking backward or too much fwd.
|
||||
@@ -211,14 +226,17 @@ static void cache_fill(struct priv *s)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!s->enable_readahead && s->read_min <= s->max_filepos) {
|
||||
s->idle = true;
|
||||
return;
|
||||
}
|
||||
if (!s->enable_readahead && s->read_min <= s->max_filepos)
|
||||
goto done;
|
||||
|
||||
if (mp_cancel_test(s->cache->cancel))
|
||||
goto done;
|
||||
|
||||
if (!s->speed_start) {
|
||||
s->speed_start = mp_time_us();
|
||||
s->speed_amount = 0;
|
||||
}
|
||||
|
||||
// number of buffer bytes which should be preserved in backwards direction
|
||||
int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size);
|
||||
|
||||
@@ -240,11 +258,8 @@ static void cache_fill(struct priv *s)
|
||||
if (pos >= s->buffer_size)
|
||||
pos -= s->buffer_size; // wrap-around
|
||||
|
||||
if (space < FILL_LIMIT) {
|
||||
s->idle = true;
|
||||
s->reads++; // don't stuck main thread
|
||||
return;
|
||||
}
|
||||
if (space < FILL_LIMIT)
|
||||
goto done;
|
||||
|
||||
// limit to end of buffer (without wrapping)
|
||||
if (pos + space >= s->buffer_size)
|
||||
@@ -274,15 +289,31 @@ static void cache_fill(struct priv *s)
|
||||
s->max_filepos += len;
|
||||
if (pos + len == s->buffer_size)
|
||||
s->offset += s->buffer_size; // wrap...
|
||||
s->speed_amount += len;
|
||||
|
||||
done:
|
||||
s->eof = len <= 0;
|
||||
s->idle = s->eof;
|
||||
s->reads++;
|
||||
if (s->eof) {
|
||||
read_attempted = true;
|
||||
|
||||
done: ;
|
||||
|
||||
bool prev_eof = s->eof;
|
||||
if (read_attempted)
|
||||
s->eof = len <= 0;
|
||||
if (!prev_eof && s->eof) {
|
||||
s->eof_pos = stream_tell(s->stream);
|
||||
MP_TRACE(s, "EOF reached.\n");
|
||||
s->speed_start = 0;
|
||||
MP_VERBOSE(s, "EOF reached.\n");
|
||||
}
|
||||
s->idle = s->eof || !read_attempted;
|
||||
s->reads++;
|
||||
|
||||
if (s->idle) {
|
||||
update_speed(s);
|
||||
s->speed_start = 0;
|
||||
}
|
||||
|
||||
int64_t now = mp_time_us();
|
||||
if (s->speed_start && s->speed_start + 1000000 <= now)
|
||||
update_speed(s);
|
||||
|
||||
pthread_cond_signal(&s->wakeup);
|
||||
}
|
||||
@@ -335,6 +366,7 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
s->buffer_size = buffer_size;
|
||||
s->buffer = buffer;
|
||||
s->idle = false;
|
||||
s->speed_start = 0;
|
||||
s->eof = false;
|
||||
|
||||
//make sure that we won't wait from cache_fill
|
||||
@@ -371,14 +403,13 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = cache->priv;
|
||||
switch (cmd) {
|
||||
case STREAM_CTRL_GET_CACHE_SIZE:
|
||||
*(int64_t *)arg = s->buffer_size - s->back_size;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_CACHE_FILL:
|
||||
*(int64_t *)arg = s->max_filepos - s->read_filepos;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_CACHE_IDLE:
|
||||
*(int *)arg = s->idle;
|
||||
case STREAM_CTRL_GET_CACHE_INFO:
|
||||
*(struct stream_cache_info *)arg = (struct stream_cache_info) {
|
||||
.size = s->buffer_size - s->back_size,
|
||||
.fill = s->max_filepos - s->read_filepos,
|
||||
.idle = s->idle,
|
||||
.speed = llrint(s->speed),
|
||||
};
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_SET_READAHEAD:
|
||||
s->enable_readahead = *(int *)arg;
|
||||
@@ -409,10 +440,6 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg)
|
||||
}
|
||||
return STREAM_UNSUPPORTED;
|
||||
}
|
||||
case STREAM_CTRL_RESUME_CACHE:
|
||||
s->idle = s->eof = false;
|
||||
pthread_cond_signal(&s->wakeup);
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_AVSEEK:
|
||||
if (!s->has_avseek)
|
||||
return STREAM_UNSUPPORTED;
|
||||
@@ -683,17 +710,15 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
|
||||
for (;;) {
|
||||
if (mp_cancel_test(cache->cancel))
|
||||
return -1;
|
||||
int64_t fill;
|
||||
int idle;
|
||||
if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_FILL, &fill) < 0)
|
||||
break;
|
||||
if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_IDLE, &idle) < 0)
|
||||
struct stream_cache_info info;
|
||||
if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_INFO, &info) < 0)
|
||||
break;
|
||||
MP_INFO(s, "\rCache fill: %5.2f%% "
|
||||
"(%" PRId64 " bytes) ", 100.0 * fill / s->buffer_size, fill);
|
||||
if (fill >= min)
|
||||
"(%" PRId64 " bytes) ", 100.0 * info.fill / s->buffer_size,
|
||||
info.fill);
|
||||
if (info.fill >= min)
|
||||
break;
|
||||
if (idle)
|
||||
if (info.idle)
|
||||
break; // file is smaller than prefill size
|
||||
// Wake up if the cache is done reading some data (or on timeout/abort)
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <inttypes.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include <linux/dvb/frontend.h>
|
||||
#include <linux/dvb/video.h>
|
||||
#include <linux/dvb/audio.h>
|
||||
#include <linux/dvb/version.h>
|
||||
|
||||
#undef DVB_ATSC
|
||||
|
||||
+9
-4
@@ -67,11 +67,8 @@ enum stream_ctrl {
|
||||
STREAM_CTRL_GET_SIZE = 1,
|
||||
|
||||
// Cache
|
||||
STREAM_CTRL_GET_CACHE_SIZE,
|
||||
STREAM_CTRL_GET_CACHE_INFO,
|
||||
STREAM_CTRL_SET_CACHE_SIZE,
|
||||
STREAM_CTRL_GET_CACHE_FILL,
|
||||
STREAM_CTRL_GET_CACHE_IDLE,
|
||||
STREAM_CTRL_RESUME_CACHE,
|
||||
STREAM_CTRL_SET_READAHEAD,
|
||||
|
||||
// stream_memory.c
|
||||
@@ -122,6 +119,14 @@ enum stream_ctrl {
|
||||
STREAM_CTRL_SET_CURRENT_TITLE,
|
||||
};
|
||||
|
||||
// for STREAM_CTRL_GET_CACHE_INFO
|
||||
struct stream_cache_info {
|
||||
int64_t size;
|
||||
int64_t fill;
|
||||
bool idle;
|
||||
int64_t speed;
|
||||
};
|
||||
|
||||
struct stream_lang_req {
|
||||
int type; // STREAM_AUDIO, STREAM_SUB
|
||||
int id;
|
||||
|
||||
+4
-4
@@ -22,6 +22,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <ass/ass.h>
|
||||
#include <ass/ass_types.h>
|
||||
@@ -76,6 +77,7 @@ void mp_ass_set_style(ASS_Style *style, double res_y,
|
||||
style->Alignment = 1 + (opts->align_x + 1) + (opts->align_y + 2) % 3 * 4;
|
||||
style->Blur = opts->blur;
|
||||
style->Bold = opts->bold;
|
||||
style->Italic = opts->italic;
|
||||
}
|
||||
|
||||
void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts,
|
||||
@@ -96,16 +98,15 @@ void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts,
|
||||
}
|
||||
|
||||
void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time,
|
||||
struct sub_bitmap **parts, struct sub_bitmaps *res)
|
||||
struct sub_bitmaps *res)
|
||||
{
|
||||
int changed;
|
||||
ASS_Image *imgs = ass_render_frame(renderer, track, time, &changed);
|
||||
if (changed)
|
||||
res->change_id++;
|
||||
assert(res->format == 0 || res->format == SUBBITMAP_LIBASS);
|
||||
res->format = SUBBITMAP_LIBASS;
|
||||
|
||||
res->parts = *parts;
|
||||
res->num_parts = 0;
|
||||
int num_parts_alloc = MP_TALLOC_AVAIL(res->parts);
|
||||
for (struct ass_image *img = imgs; img; img = img->next) {
|
||||
if (img->w == 0 || img->h == 0)
|
||||
@@ -125,7 +126,6 @@ void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time,
|
||||
p->y = img->dst_y;
|
||||
res->num_parts++;
|
||||
}
|
||||
*parts = res->parts;
|
||||
}
|
||||
|
||||
static const int map_ass_level[] = {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ ASS_Library *mp_ass_init(struct mpv_global *global, struct mp_log *log);
|
||||
struct sub_bitmap;
|
||||
struct sub_bitmaps;
|
||||
void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time,
|
||||
struct sub_bitmap **parts, struct sub_bitmaps *res);
|
||||
struct sub_bitmaps *res);
|
||||
|
||||
#endif /* HAVE_LIBASS */
|
||||
#endif /* MPLAYER_ASS_MP_H */
|
||||
+32
-17
@@ -49,10 +49,11 @@ struct dec_sub {
|
||||
struct mpv_global *global;
|
||||
struct MPOpts *opts;
|
||||
|
||||
struct demuxer *demuxer;
|
||||
struct attachment_list *attachments;
|
||||
|
||||
struct sh_stream *sh;
|
||||
double last_pkt_pts;
|
||||
bool preload_attempted;
|
||||
|
||||
struct mp_codec_params *codec;
|
||||
double start, end;
|
||||
@@ -94,8 +95,9 @@ static struct sd *init_decoder(struct dec_sub *sub)
|
||||
.log = mp_log_new(sd, sub->log, driver->name),
|
||||
.opts = sub->opts,
|
||||
.driver = driver,
|
||||
.demuxer = sub->demuxer,
|
||||
.attachments = sub->attachments,
|
||||
.codec = sub->codec,
|
||||
.preload_ok = true,
|
||||
};
|
||||
|
||||
if (sd->driver->init(sd) >= 0)
|
||||
@@ -112,10 +114,12 @@ static struct sd *init_decoder(struct dec_sub *sub)
|
||||
// Thread-safety of the returned object: all functions are thread-safe,
|
||||
// except sub_get_bitmaps() and sub_get_text(). Decoder backends (sd_*)
|
||||
// do not need to acquire locks.
|
||||
struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer,
|
||||
struct sh_stream *sh)
|
||||
// Ownership of attachments goes to the caller, and is released with
|
||||
// talloc_free() (even on failure).
|
||||
struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh,
|
||||
struct attachment_list *attachments)
|
||||
{
|
||||
assert(demuxer && sh && sh->type == STREAM_SUB);
|
||||
assert(sh && sh->type == STREAM_SUB);
|
||||
|
||||
struct dec_sub *sub = talloc(NULL, struct dec_sub);
|
||||
*sub = (struct dec_sub){
|
||||
@@ -124,7 +128,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer,
|
||||
.opts = global->opts,
|
||||
.sh = sh,
|
||||
.codec = sh->codec,
|
||||
.demuxer = demuxer,
|
||||
.attachments = talloc_steal(sub, attachments),
|
||||
.last_pkt_pts = MP_NOPTS_VALUE,
|
||||
.last_vo_pts = MP_NOPTS_VALUE,
|
||||
.start = MP_NOPTS_VALUE,
|
||||
@@ -146,6 +150,9 @@ static void update_segment(struct dec_sub *sub)
|
||||
if (sub->new_segment && sub->last_vo_pts != MP_NOPTS_VALUE &&
|
||||
sub->last_vo_pts >= sub->new_segment->start)
|
||||
{
|
||||
MP_VERBOSE(sub, "Switch segment: %f at %f\n", sub->new_segment->start,
|
||||
sub->last_vo_pts);
|
||||
|
||||
sub->codec = sub->new_segment->codec;
|
||||
sub->start = sub->new_segment->start;
|
||||
sub->end = sub->new_segment->end;
|
||||
@@ -165,16 +172,20 @@ static void update_segment(struct dec_sub *sub)
|
||||
}
|
||||
}
|
||||
|
||||
// Read all packets from the demuxer and decode/add them. Returns false if
|
||||
// there are circumstances which makes this not possible.
|
||||
bool sub_read_all_packets(struct dec_sub *sub)
|
||||
bool sub_can_preload(struct dec_sub *sub)
|
||||
{
|
||||
bool r;
|
||||
pthread_mutex_lock(&sub->lock);
|
||||
r = sub->sd->driver->accept_packets_in_advance && !sub->preload_attempted;
|
||||
pthread_mutex_unlock(&sub->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
void sub_preload(struct dec_sub *sub)
|
||||
{
|
||||
pthread_mutex_lock(&sub->lock);
|
||||
|
||||
if (!sub->sd->driver->accept_packets_in_advance) {
|
||||
pthread_mutex_unlock(&sub->lock);
|
||||
return false;
|
||||
}
|
||||
sub->preload_attempted = true;
|
||||
|
||||
for (;;) {
|
||||
struct demux_packet *pkt = demux_read_packet(sub->sh);
|
||||
@@ -185,7 +196,6 @@ bool sub_read_all_packets(struct dec_sub *sub)
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&sub->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read packets from the demuxer stream passed to sub_create(). Return true if
|
||||
@@ -203,6 +213,11 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
|
||||
if (!read_more)
|
||||
break;
|
||||
|
||||
if (sub->new_segment && sub->new_segment->start < video_pts) {
|
||||
sub->last_vo_pts = video_pts;
|
||||
update_segment(sub);
|
||||
}
|
||||
|
||||
if (sub->new_segment)
|
||||
break;
|
||||
|
||||
@@ -228,7 +243,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
|
||||
break;
|
||||
}
|
||||
|
||||
sub->sd->driver->decode(sub->sd, pkt);
|
||||
if (!(sub->preload_attempted && sub->sd->preload_ok))
|
||||
sub->sd->driver->decode(sub->sd, pkt);
|
||||
|
||||
talloc_free(pkt);
|
||||
}
|
||||
pthread_mutex_unlock(&sub->lock);
|
||||
@@ -243,8 +260,6 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
{
|
||||
struct MPOpts *opts = sub->opts;
|
||||
|
||||
*res = (struct sub_bitmaps) {0};
|
||||
|
||||
sub->last_vo_pts = pts;
|
||||
update_segment(sub);
|
||||
|
||||
|
||||
+9
-4
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "osd.h"
|
||||
|
||||
struct demuxer;
|
||||
struct sh_stream;
|
||||
struct mpv_global;
|
||||
struct demux_packet;
|
||||
@@ -22,13 +21,19 @@ enum sd_ctrl {
|
||||
SD_CTRL_SET_VIDEO_DEF_FPS,
|
||||
};
|
||||
|
||||
struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer,
|
||||
struct sh_stream *sh);
|
||||
struct attachment_list {
|
||||
struct demux_attachment *entries;
|
||||
int num_entries;
|
||||
};
|
||||
|
||||
struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh,
|
||||
struct attachment_list *attachments);
|
||||
void sub_destroy(struct dec_sub *sub);
|
||||
void sub_lock(struct dec_sub *sub);
|
||||
void sub_unlock(struct dec_sub *sub);
|
||||
|
||||
bool sub_read_all_packets(struct dec_sub *sub);
|
||||
bool sub_can_preload(struct dec_sub *sub);
|
||||
void sub_preload(struct dec_sub *sub);
|
||||
bool sub_read_packets(struct dec_sub *sub, double video_pts);
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res);
|
||||
|
||||
+4
-4
@@ -47,6 +47,9 @@ static const char *get_lavc_format(const char *format)
|
||||
// For the hack involving parse_webvtt().
|
||||
if (format && strcmp(format, "webvtt-webm") == 0)
|
||||
format = "webvtt";
|
||||
// Most text subtitles are srt/html style anyway.
|
||||
if (format && strcmp(format, "text") == 0)
|
||||
format = "subrip";
|
||||
return format;
|
||||
}
|
||||
|
||||
@@ -79,11 +82,8 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, const char *codec_name,
|
||||
avctx = avcodec_alloc_context3(codec);
|
||||
if (!avctx)
|
||||
goto error;
|
||||
avctx->extradata_size = extradata_len;
|
||||
avctx->extradata = av_malloc(extradata_len);
|
||||
if (!avctx->extradata)
|
||||
if (mp_lavc_set_extradata(avctx, extradata, extradata_len) < 0)
|
||||
goto error;
|
||||
memcpy(avctx->extradata, extradata, extradata_len);
|
||||
if (strcmp(codec_name, "eia_608") == 0)
|
||||
av_dict_set(&opts, "real_time", "1", 0);
|
||||
if (avcodec_open2(avctx, codec, &opts) < 0)
|
||||
|
||||
@@ -61,6 +61,7 @@ static const m_option_t style_opts[] = {
|
||||
({"top", -1}, {"center", 0}, {"bottom", +1})),
|
||||
OPT_FLOATRANGE("blur", blur, 0, 0, 20),
|
||||
OPT_FLAG("bold", bold, 0),
|
||||
OPT_FLAG("italic", italic, 0),
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -109,6 +110,8 @@ static bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b)
|
||||
|
||||
struct osd_state *osd_create(struct mpv_global *global)
|
||||
{
|
||||
assert(MAX_OSD_PARTS >= OSDTYPE_COUNT);
|
||||
|
||||
struct osd_state *osd = talloc_zero(NULL, struct osd_state);
|
||||
*osd = (struct osd_state) {
|
||||
.opts = global->opts,
|
||||
@@ -145,30 +148,31 @@ void osd_free(struct osd_state *osd)
|
||||
talloc_free(osd);
|
||||
}
|
||||
|
||||
static void osd_changed_unlocked(struct osd_state *osd, int obj)
|
||||
void osd_changed_unlocked(struct osd_state *osd, int obj)
|
||||
{
|
||||
osd->objs[obj]->force_redraw = true;
|
||||
osd->want_redraw = true;
|
||||
}
|
||||
|
||||
void osd_set_text(struct osd_state *osd, int obj, const char *text)
|
||||
void osd_set_text(struct osd_state *osd, const char *text)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
struct osd_object *osd_obj = osd->objs[obj];
|
||||
struct osd_object *osd_obj = osd->objs[OSDTYPE_OSD];
|
||||
if (!text)
|
||||
text = "";
|
||||
if (strcmp(osd_obj->text, text) != 0) {
|
||||
talloc_free(osd_obj->text);
|
||||
osd_obj->text = talloc_strdup(osd_obj, text);
|
||||
osd_changed_unlocked(osd, obj);
|
||||
osd_changed_unlocked(osd, osd_obj->type);
|
||||
}
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
void osd_set_sub(struct osd_state *osd, int obj, struct dec_sub *dec_sub)
|
||||
void osd_set_sub(struct osd_state *osd, int index, struct dec_sub *dec_sub)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
osd->objs[obj]->sub = dec_sub;
|
||||
if (index >= 0 && index < 2)
|
||||
osd->objs[OSDTYPE_SUB + index]->sub = dec_sub;
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
@@ -201,23 +205,6 @@ void osd_set_progbar(struct osd_state *osd, struct osd_progbar_state *s)
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
void osd_set_external(struct osd_state *osd, int res_x, int res_y, char *text)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
struct osd_object *osd_obj = osd->objs[OSDTYPE_EXTERNAL];
|
||||
if (strcmp(osd_obj->text, text) != 0 ||
|
||||
osd_obj->external_res_x != res_x ||
|
||||
osd_obj->external_res_y != res_y)
|
||||
{
|
||||
talloc_free(osd_obj->text);
|
||||
osd_obj->text = talloc_strdup(osd_obj, text);
|
||||
osd_obj->external_res_x = res_x;
|
||||
osd_obj->external_res_y = res_y;
|
||||
osd_changed_unlocked(osd, osd_obj->type);
|
||||
}
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
@@ -226,6 +213,33 @@ void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs)
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
static void check_obj_resize(struct osd_state *osd, struct mp_osd_res res,
|
||||
struct osd_object *obj)
|
||||
{
|
||||
if (!osd_res_equals(res, obj->vo_res)) {
|
||||
obj->vo_res = res;
|
||||
obj->force_redraw = true;
|
||||
mp_client_broadcast_event(mp_client_api_get_core(osd->global->client_api),
|
||||
MP_EVENT_WIN_RESIZE, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Optional. Can be called for faster reaction of OSD-generating scripts like
|
||||
// osc.lua. This can achieve that the resize happens first, so that the OSD is
|
||||
// generated at the correct resolution the first time the resized frame is
|
||||
// rendered. Since the OSD doesn't (and can't) wait for the script, this
|
||||
// increases the time in which the script can react, and also gets rid of the
|
||||
// unavoidable redraw delay (though it will still be racy).
|
||||
// Unnecessary for anything else.
|
||||
void osd_resize(struct osd_state *osd, struct mp_osd_res res)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
int types[] = {OSDTYPE_OSD, OSDTYPE_EXTERNAL, OSDTYPE_EXTERNAL2, -1};
|
||||
for (int n = 0; types[n] >= 0; n++)
|
||||
check_obj_resize(osd, res, osd->objs[types[n]]);
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
struct mp_osd_res res, double video_pts,
|
||||
const bool sub_formats[SUBBITMAP_COUNT],
|
||||
@@ -240,12 +254,7 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
|
||||
*out_imgs = (struct sub_bitmaps) {0};
|
||||
|
||||
if (!osd_res_equals(res, obj->vo_res)) {
|
||||
obj->vo_res = res;
|
||||
obj->force_redraw = true;
|
||||
mp_client_broadcast_event(mp_client_api_get_core(osd->global->client_api),
|
||||
MP_EVENT_WIN_RESIZE, NULL);
|
||||
}
|
||||
check_obj_resize(osd, res, obj);
|
||||
|
||||
if (obj->type == OSDTYPE_SUB || obj->type == OSDTYPE_SUB2) {
|
||||
if (obj->sub) {
|
||||
@@ -422,44 +431,11 @@ bool osd_query_and_reset_want_redraw(struct osd_state *osd)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Scale factor to translate OSD coordinates to what the obj uses internally.
|
||||
// osd_coordinates * (sw, sh) = obj_coordinates
|
||||
void osd_object_get_scale_factor(struct osd_state *osd, int obj,
|
||||
double *sw, double *sh)
|
||||
{
|
||||
int nw, nh;
|
||||
osd_object_get_resolution(osd, obj, &nw, &nh);
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
int vow = osd->objs[obj]->vo_res.w;
|
||||
int voh = osd->objs[obj]->vo_res.h;
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
*sw = vow ? nw / (double)vow : 0;
|
||||
*sh = voh ? nh / (double)voh : 0;
|
||||
}
|
||||
|
||||
// Turn *x and *y, which are given in OSD coordinates, to video coordinates.
|
||||
// frame_w and frame_h give the dimensions of the original, unscaled video.
|
||||
// (This gives correct results only after the OSD has been updated after a
|
||||
// resize or video reconfig.)
|
||||
void osd_coords_to_video(struct osd_state *osd, int frame_w, int frame_h,
|
||||
int *x, int *y)
|
||||
struct mp_osd_res osd_get_vo_res(struct osd_state *osd)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
// Any OSDTYPE is fine; but it mustn't be a subtitle one (can have lower res.)
|
||||
struct mp_osd_res res = osd->objs[OSDTYPE_OSD]->vo_res;
|
||||
int vidw = res.w - res.ml - res.mr;
|
||||
int vidh = res.h - res.mt - res.mb;
|
||||
double xscale = (double)vidw / frame_w;
|
||||
double yscale = (double)vidh / frame_h;
|
||||
// The OSD size + margins make up the scaled rectangle of the video.
|
||||
*x = (*x - res.ml) / xscale;
|
||||
*y = (*y - res.mt) / yscale;
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
struct mp_osd_res osd_get_vo_res(struct osd_state *osd, int obj)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
struct mp_osd_res res = osd->objs[obj]->vo_res;
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -78,17 +78,8 @@ struct mp_osd_res {
|
||||
double display_par;
|
||||
};
|
||||
|
||||
enum mp_osdtype {
|
||||
OSDTYPE_SUB,
|
||||
OSDTYPE_SUB2, // IDs must be numerically successive
|
||||
|
||||
OSDTYPE_OSD,
|
||||
|
||||
OSDTYPE_EXTERNAL,
|
||||
OSDTYPE_EXTERNAL2,
|
||||
|
||||
MAX_OSD_PARTS
|
||||
};
|
||||
// 0 <= sub_bitmaps.render_index < MAX_OSD_PARTS
|
||||
#define MAX_OSD_PARTS 5
|
||||
|
||||
// Start of OSD symbols in osd_font.pfb
|
||||
#define OSD_CODEPOINTS 0xE000
|
||||
@@ -132,6 +123,7 @@ struct osd_style_opts {
|
||||
int align_y;
|
||||
float blur;
|
||||
int bold;
|
||||
int italic;
|
||||
};
|
||||
|
||||
extern const struct m_sub_options osd_style_conf;
|
||||
@@ -149,8 +141,8 @@ void osd_free(struct osd_state *osd);
|
||||
|
||||
bool osd_query_and_reset_want_redraw(struct osd_state *osd);
|
||||
|
||||
void osd_set_text(struct osd_state *osd, int obj, const char *text);
|
||||
void osd_set_sub(struct osd_state *osd, int obj, struct dec_sub *dec_sub);
|
||||
void osd_set_text(struct osd_state *osd, const char *text);
|
||||
void osd_set_sub(struct osd_state *osd, int index, struct dec_sub *dec_sub);
|
||||
|
||||
bool osd_get_render_subs_in_filter(struct osd_state *osd);
|
||||
void osd_set_render_subs_in_filter(struct osd_state *osd, bool s);
|
||||
@@ -163,8 +155,6 @@ struct osd_progbar_state {
|
||||
};
|
||||
void osd_set_progbar(struct osd_state *osd, struct osd_progbar_state *s);
|
||||
|
||||
void osd_set_external(struct osd_state *osd, int res_x, int res_y, char *text);
|
||||
|
||||
void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs);
|
||||
|
||||
enum mp_osd_draw_flags {
|
||||
@@ -187,16 +177,12 @@ void osd_draw_on_image_p(struct osd_state *osd, struct mp_osd_res res,
|
||||
double video_pts, int draw_flags,
|
||||
struct mp_image_pool *pool, struct mp_image *dest);
|
||||
|
||||
void osd_resize(struct osd_state *osd, struct mp_osd_res res);
|
||||
|
||||
struct mp_image_params;
|
||||
struct mp_osd_res osd_res_from_image_params(const struct mp_image_params *p);
|
||||
|
||||
void osd_object_get_scale_factor(struct osd_state *osd, int obj,
|
||||
double *sw, double *sh);
|
||||
|
||||
void osd_coords_to_video(struct osd_state *osd, int frame_w, int frame_h,
|
||||
int *x, int *y);
|
||||
|
||||
struct mp_osd_res osd_get_vo_res(struct osd_state *osd, int obj);
|
||||
struct mp_osd_res osd_get_vo_res(struct osd_state *osd);
|
||||
|
||||
void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h,
|
||||
struct mp_osd_res res, double compensate_par);
|
||||
@@ -209,13 +195,12 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
void osd_init_backend(struct osd_state *osd);
|
||||
void osd_destroy_backend(struct osd_state *osd);
|
||||
|
||||
void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
|
||||
char *text);
|
||||
|
||||
// doesn't need locking
|
||||
void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function);
|
||||
extern const char *const osd_ass_0;
|
||||
extern const char *const osd_ass_1;
|
||||
|
||||
// defined in backend, but locks if required
|
||||
void osd_object_get_resolution(struct osd_state *osd, int obj,
|
||||
int *out_w, int *out_h);
|
||||
|
||||
#endif /* MPLAYER_SUB_H */
|
||||
+2
-4
@@ -27,9 +27,7 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
*out_imgs = (struct sub_bitmaps) {0};
|
||||
}
|
||||
|
||||
void osd_object_get_resolution(struct osd_state *osd, int obj,
|
||||
int *out_w, int *out_h)
|
||||
void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
|
||||
char *text)
|
||||
{
|
||||
*out_w = 0;
|
||||
*out_h = 0;
|
||||
}
|
||||
+130
-79
@@ -45,49 +45,66 @@ void osd_init_backend(struct osd_state *osd)
|
||||
{
|
||||
}
|
||||
|
||||
static void create_ass_renderer(struct osd_state *osd, struct osd_object *obj)
|
||||
static void create_ass_renderer(struct osd_state *osd, struct ass_state *ass)
|
||||
{
|
||||
if (obj->osd_render)
|
||||
if (ass->render)
|
||||
return;
|
||||
|
||||
struct mp_log *ass_log = mp_log_new(obj, osd->log, "libass");
|
||||
obj->osd_ass_library = mp_ass_init(osd->global, ass_log);
|
||||
ass_add_font(obj->osd_ass_library, "mpv-osd-symbols", (void *)osd_font_pfb,
|
||||
ass->log = mp_log_new(NULL, osd->log, "libass");
|
||||
ass->library = mp_ass_init(osd->global, ass->log);
|
||||
ass_add_font(ass->library, "mpv-osd-symbols", (void *)osd_font_pfb,
|
||||
sizeof(osd_font_pfb) - 1);
|
||||
|
||||
obj->osd_render = ass_renderer_init(obj->osd_ass_library);
|
||||
if (!obj->osd_render)
|
||||
ass->render = ass_renderer_init(ass->library);
|
||||
if (!ass->render)
|
||||
abort();
|
||||
|
||||
mp_ass_configure_fonts(obj->osd_render, osd->opts->osd_style,
|
||||
osd->global, ass_log);
|
||||
ass_set_aspect_ratio(obj->osd_render, 1.0, 1.0);
|
||||
mp_ass_configure_fonts(ass->render, osd->opts->osd_style,
|
||||
osd->global, ass->log);
|
||||
ass_set_aspect_ratio(ass->render, 1.0, 1.0);
|
||||
}
|
||||
|
||||
static void destroy_ass_renderer(struct ass_state *ass)
|
||||
{
|
||||
if (ass->track)
|
||||
ass_free_track(ass->track);
|
||||
ass->track = NULL;
|
||||
if (ass->render)
|
||||
ass_renderer_done(ass->render);
|
||||
ass->render = NULL;
|
||||
if (ass->library)
|
||||
ass_library_done(ass->library);
|
||||
ass->library = NULL;
|
||||
talloc_free(ass->log);
|
||||
ass->log = NULL;
|
||||
}
|
||||
|
||||
static void destroy_external(struct osd_external *ext)
|
||||
{
|
||||
talloc_free(ext->text);
|
||||
destroy_ass_renderer(&ext->ass);
|
||||
}
|
||||
|
||||
void osd_destroy_backend(struct osd_state *osd)
|
||||
{
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
||||
struct osd_object *obj = osd->objs[n];
|
||||
if (obj->osd_track)
|
||||
ass_free_track(obj->osd_track);
|
||||
obj->osd_track = NULL;
|
||||
if (obj->osd_render)
|
||||
ass_renderer_done(obj->osd_render);
|
||||
obj->osd_render = NULL;
|
||||
if (obj->osd_ass_library)
|
||||
ass_library_done(obj->osd_ass_library);
|
||||
obj->osd_ass_library = NULL;
|
||||
destroy_ass_renderer(&obj->ass);
|
||||
talloc_free(obj->parts_cache.parts);
|
||||
for (int i = 0; i < obj->num_externals; i++)
|
||||
destroy_external(&obj->externals[i]);
|
||||
obj->num_externals = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_ass_track(struct osd_state *osd, struct osd_object *obj,
|
||||
int res_x, int res_y)
|
||||
struct ass_state *ass, int res_x, int res_y)
|
||||
{
|
||||
create_ass_renderer(osd, obj);
|
||||
create_ass_renderer(osd, ass);
|
||||
|
||||
ASS_Track *track = obj->osd_track;
|
||||
ASS_Track *track = ass->track;
|
||||
if (!track)
|
||||
track = ass_new_track(obj->osd_ass_library);
|
||||
track = ass_new_track(ass->library);
|
||||
|
||||
int old_res_x = track->PlayResX;
|
||||
int old_res_y = track->PlayResY;
|
||||
@@ -105,9 +122,9 @@ static void create_ass_track(struct osd_state *osd, struct osd_object *obj,
|
||||
// Force libass to clear its internal cache - it doesn't check for
|
||||
// PlayRes changes itself.
|
||||
if (old_res_x != track->PlayResX || old_res_y != track->PlayResY)
|
||||
ass_set_frame_size(obj->osd_render, 1, 1);
|
||||
ass_set_frame_size(ass->render, 1, 1);
|
||||
|
||||
obj->osd_track = track;
|
||||
ass->track = track;
|
||||
}
|
||||
|
||||
static int find_style(ASS_Track *track, const char *name, int def)
|
||||
@@ -120,10 +137,9 @@ static int find_style(ASS_Track *track, const char *name, int def)
|
||||
}
|
||||
|
||||
// Find a given style, or add it if it's missing.
|
||||
static ASS_Style *get_style(struct osd_state *osd, struct osd_object *obj,
|
||||
char *name)
|
||||
static ASS_Style *get_style(struct ass_state *ass, char *name)
|
||||
{
|
||||
ASS_Track *track = obj->osd_track;
|
||||
ASS_Track *track = ass->track;
|
||||
if (!track)
|
||||
return NULL;
|
||||
|
||||
@@ -154,10 +170,10 @@ static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *style,
|
||||
return event;
|
||||
}
|
||||
|
||||
static void clear_obj(struct osd_object *obj)
|
||||
static void clear_ass(struct ass_state *ass)
|
||||
{
|
||||
if (obj->osd_track)
|
||||
ass_flush_events(obj->osd_track);
|
||||
if (ass->track)
|
||||
ass_flush_events(ass->track);
|
||||
}
|
||||
|
||||
void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function)
|
||||
@@ -214,18 +230,18 @@ static void update_osd_text(struct osd_state *osd, struct osd_object *obj)
|
||||
if (!obj->text[0])
|
||||
return;
|
||||
|
||||
create_ass_track(osd, obj, 0, 0);
|
||||
create_ass_track(osd, obj, &obj->ass, 0, 0);
|
||||
|
||||
struct osd_style_opts font = *opts->osd_style;
|
||||
font.font_size *= opts->osd_scale;
|
||||
|
||||
double playresy = obj->osd_track->PlayResY;
|
||||
double playresy = obj->ass.track->PlayResY;
|
||||
// Compensate for libass and mp_ass_set_style scaling the font etc.
|
||||
if (!opts->osd_scale_by_window)
|
||||
playresy *= 720.0 / obj->vo_res.h;
|
||||
|
||||
mp_ass_set_style(get_style(osd, obj, "OSD"), playresy, &font);
|
||||
add_osd_ass_event_escaped(obj->osd_track, "OSD", obj->text);
|
||||
mp_ass_set_style(get_style(&obj->ass, "OSD"), playresy, &font);
|
||||
add_osd_ass_event_escaped(obj->ass.track, "OSD", obj->text);
|
||||
}
|
||||
|
||||
// align: -1 .. +1
|
||||
@@ -308,10 +324,10 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
|
||||
{
|
||||
struct MPOpts *opts = osd->opts;
|
||||
|
||||
create_ass_track(osd, obj, 0, 0);
|
||||
ASS_Track *track = obj->osd_track;
|
||||
create_ass_track(osd, obj, &obj->ass, 0, 0);
|
||||
ASS_Track *track = obj->ass.track;
|
||||
|
||||
ASS_Style *style = get_style(osd, obj, "progbar");
|
||||
ASS_Style *style = get_style(&obj->ass, "progbar");
|
||||
if (!style) {
|
||||
*o_x = *o_y = *o_w = *o_h = *o_border = 0;
|
||||
return;
|
||||
@@ -347,6 +363,8 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
float px, py, width, height, border;
|
||||
get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border);
|
||||
|
||||
ASS_Track *track = obj->ass.track;
|
||||
|
||||
float sx = px - border * 2 - height / 4; // includes additional spacing
|
||||
float sy = py + height / 2;
|
||||
|
||||
@@ -362,7 +380,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
bstr_xappend(NULL, &buf, bstr0("{\\r}"));
|
||||
}
|
||||
|
||||
add_osd_ass_event(obj->osd_track, "progbar", buf.start);
|
||||
add_osd_ass_event(track, "progbar", buf.start);
|
||||
talloc_free(buf.start);
|
||||
|
||||
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
|
||||
@@ -372,7 +390,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
float pos = obj->progbar_state.value * width - border / 2;
|
||||
ass_draw_rect_cw(d, 0, 0, pos, height);
|
||||
ass_draw_stop(d);
|
||||
add_osd_ass_event(obj->osd_track, "progbar", d->text);
|
||||
add_osd_ass_event(track, "progbar", d->text);
|
||||
ass_draw_reset(d);
|
||||
|
||||
// position marker
|
||||
@@ -382,7 +400,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
ass_draw_move_to(d, pos + border / 2, 0);
|
||||
ass_draw_line_to(d, pos + border / 2, height);
|
||||
ass_draw_stop(d);
|
||||
add_osd_ass_event(obj->osd_track, "progbar", d->text);
|
||||
add_osd_ass_event(track, "progbar", d->text);
|
||||
ass_draw_reset(d);
|
||||
|
||||
d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py);
|
||||
@@ -411,79 +429,112 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
}
|
||||
|
||||
ass_draw_stop(d);
|
||||
add_osd_ass_event(obj->osd_track, "progbar", d->text);
|
||||
add_osd_ass_event(track, "progbar", d->text);
|
||||
ass_draw_reset(d);
|
||||
}
|
||||
|
||||
static void update_osd(struct osd_state *osd, struct osd_object *obj)
|
||||
{
|
||||
clear_obj(obj);
|
||||
clear_ass(&obj->ass);
|
||||
update_osd_text(osd, obj);
|
||||
update_progbar(osd, obj);
|
||||
}
|
||||
|
||||
static void update_external(struct osd_state *osd, struct osd_object *obj)
|
||||
static void update_external(struct osd_state *osd, struct osd_object *obj,
|
||||
struct osd_external *ext)
|
||||
{
|
||||
clear_obj(obj);
|
||||
|
||||
bstr t = bstr0(obj->text);
|
||||
bstr t = bstr0(ext->text);
|
||||
if (!t.len)
|
||||
return;
|
||||
create_ass_track(osd, obj, obj->external_res_x, obj->external_res_y);
|
||||
create_ass_track(osd, obj, &ext->ass, ext->res_x, ext->res_y);
|
||||
|
||||
int resy = obj->osd_track->PlayResY;
|
||||
mp_ass_set_style(get_style(osd, obj, "OSD"), resy, osd->opts->osd_style);
|
||||
clear_ass(&ext->ass);
|
||||
|
||||
int resy = ext->ass.track->PlayResY;
|
||||
mp_ass_set_style(get_style(&ext->ass, "OSD"), resy, osd->opts->osd_style);
|
||||
|
||||
// Some scripts will reference this style name with \r tags.
|
||||
const struct osd_style_opts *def = osd_style_conf.defaults;
|
||||
mp_ass_set_style(get_style(osd, obj, "Default"), resy, def);
|
||||
mp_ass_set_style(get_style(&ext->ass, "Default"), resy, def);
|
||||
|
||||
while (t.len) {
|
||||
bstr line;
|
||||
bstr_split_tok(t, "\n", &line, &t);
|
||||
if (line.len) {
|
||||
char *tmp = bstrdup0(NULL, line);
|
||||
add_osd_ass_event(obj->osd_track, "OSD", tmp);
|
||||
add_osd_ass_event(ext->ass.track, "OSD", tmp);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_object(struct osd_state *osd, struct osd_object *obj)
|
||||
void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
|
||||
char *text)
|
||||
{
|
||||
switch (obj->type) {
|
||||
case OSDTYPE_OSD:
|
||||
update_osd(osd, obj);
|
||||
break;
|
||||
case OSDTYPE_EXTERNAL:
|
||||
update_external(osd, obj);
|
||||
break;
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL];
|
||||
struct osd_external *entry = 0;
|
||||
for (int n = 0; n < obj->num_externals; n++) {
|
||||
if (obj->externals[n].id == id) {
|
||||
entry = &obj->externals[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!entry && !text)
|
||||
goto done;
|
||||
|
||||
if (!entry) {
|
||||
struct osd_external new = { .id = id };
|
||||
MP_TARRAY_APPEND(obj, obj->externals, obj->num_externals, new);
|
||||
entry = &obj->externals[obj->num_externals - 1];
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
int index = entry - &obj->externals[0];
|
||||
destroy_external(entry);
|
||||
MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!entry->text || strcmp(entry->text, text) != 0 ||
|
||||
entry->res_x != res_x || entry->res_y != res_y)
|
||||
{
|
||||
talloc_free(entry->text);
|
||||
entry->text = talloc_strdup(NULL, text);
|
||||
entry->res_x = res_x;
|
||||
entry->res_y = res_y;
|
||||
update_external(osd, obj, entry);
|
||||
obj->parts_cache.change_id = 1;
|
||||
osd_changed_unlocked(osd, obj->type);
|
||||
}
|
||||
|
||||
done:
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
}
|
||||
|
||||
static void append_ass(struct ass_state *ass, struct mp_osd_res *res,
|
||||
struct sub_bitmaps *imgs)
|
||||
{
|
||||
if (!ass->render || !ass->track)
|
||||
return;
|
||||
|
||||
ass_set_frame_size(ass->render, res->w, res->h);
|
||||
ass_set_aspect_ratio(ass->render, res->display_par, 1.0);
|
||||
mp_ass_render_frame(ass->render, ass->track, 0, imgs);
|
||||
}
|
||||
|
||||
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
struct sub_bitmaps *out_imgs)
|
||||
{
|
||||
if (obj->force_redraw)
|
||||
update_object(osd, obj);
|
||||
if (obj->force_redraw && obj->type == OSDTYPE_OSD)
|
||||
update_osd(osd, obj);
|
||||
|
||||
*out_imgs = (struct sub_bitmaps) {0};
|
||||
if (!obj->osd_track)
|
||||
return;
|
||||
append_ass(&obj->ass, &obj->vo_res, &obj->parts_cache);
|
||||
for (int n = 0; n < obj->num_externals; n++)
|
||||
append_ass(&obj->externals[n].ass, &obj->vo_res, &obj->parts_cache);
|
||||
|
||||
ass_set_frame_size(obj->osd_render, obj->vo_res.w, obj->vo_res.h);
|
||||
ass_set_aspect_ratio(obj->osd_render, obj->vo_res.display_par, 1.0);
|
||||
mp_ass_render_frame(obj->osd_render, obj->osd_track, 0,
|
||||
&obj->parts_cache, out_imgs);
|
||||
talloc_steal(obj, obj->parts_cache);
|
||||
}
|
||||
*out_imgs = obj->parts_cache;
|
||||
|
||||
void osd_object_get_resolution(struct osd_state *osd, int obj,
|
||||
int *out_w, int *out_h)
|
||||
{
|
||||
pthread_mutex_lock(&osd->lock);
|
||||
struct osd_object *osd_obj = osd->objs[obj];
|
||||
*out_w = osd_obj->osd_track ? osd_obj->osd_track->PlayResX : 0;
|
||||
*out_h = osd_obj->osd_track ? osd_obj->osd_track->PlayResY : 0;
|
||||
pthread_mutex_unlock(&osd->lock);
|
||||
obj->parts_cache.change_id = 0;
|
||||
obj->parts_cache.num_parts = 0;
|
||||
}
|
||||
+33
-6
@@ -7,13 +7,32 @@
|
||||
|
||||
#define OSD_CONV_CACHE_MAX 4
|
||||
|
||||
enum mp_osdtype {
|
||||
OSDTYPE_SUB,
|
||||
OSDTYPE_SUB2, // IDs must be numerically successive
|
||||
|
||||
OSDTYPE_OSD,
|
||||
|
||||
OSDTYPE_EXTERNAL,
|
||||
OSDTYPE_EXTERNAL2,
|
||||
|
||||
OSDTYPE_COUNT
|
||||
};
|
||||
|
||||
struct ass_state {
|
||||
struct mp_log *log;
|
||||
struct ass_track *track;
|
||||
struct ass_renderer *render;
|
||||
struct ass_library *library;
|
||||
};
|
||||
|
||||
struct osd_object {
|
||||
int type; // OSDTYPE_*
|
||||
bool is_sub;
|
||||
|
||||
bool force_redraw;
|
||||
|
||||
// OSDTYPE_SUB/OSDTYPE_SUB2/OSDTYPE_OSD/OSDTYPE_EXTERNAL
|
||||
// OSDTYPE_OSD
|
||||
char *text;
|
||||
|
||||
// OSDTYPE_OSD
|
||||
@@ -23,7 +42,8 @@ struct osd_object {
|
||||
struct dec_sub *sub;
|
||||
|
||||
// OSDTYPE_EXTERNAL
|
||||
int external_res_x, external_res_y;
|
||||
struct osd_external *externals;
|
||||
int num_externals;
|
||||
|
||||
// OSDTYPE_EXTERNAL2
|
||||
struct sub_bitmaps *external2;
|
||||
@@ -37,10 +57,15 @@ struct osd_object {
|
||||
struct mp_osd_res vo_res;
|
||||
|
||||
// Internally used by osd_libass.c
|
||||
struct sub_bitmap *parts_cache;
|
||||
struct ass_track *osd_track;
|
||||
struct ass_renderer *osd_render;
|
||||
struct ass_library *osd_ass_library;
|
||||
struct sub_bitmaps parts_cache;
|
||||
struct ass_state ass;
|
||||
};
|
||||
|
||||
struct osd_external {
|
||||
void *id;
|
||||
char *text;
|
||||
int res_x, res_y;
|
||||
struct ass_state ass;
|
||||
};
|
||||
|
||||
struct osd_state {
|
||||
@@ -59,4 +84,6 @@ struct osd_state {
|
||||
struct mp_draw_sub_cache *draw_cache;
|
||||
};
|
||||
|
||||
void osd_changed_unlocked(struct osd_state *osd, int obj);
|
||||
|
||||
#endif
|
||||
@@ -17,8 +17,12 @@ struct sd {
|
||||
const struct sd_functions *driver;
|
||||
void *priv;
|
||||
|
||||
struct demuxer *demuxer;
|
||||
struct attachment_list *attachments;
|
||||
struct mp_codec_params *codec;
|
||||
|
||||
// Set to false as soon as the decoder discards old subtitle events.
|
||||
// (only needed if sd_functions.accept_packets_in_advance == false)
|
||||
bool preload_ok;
|
||||
};
|
||||
|
||||
struct sd_functions {
|
||||
|
||||
+30
-10
@@ -44,7 +44,7 @@ struct sd_ass_priv {
|
||||
bool is_converted;
|
||||
struct lavc_conv *converter;
|
||||
bool on_top;
|
||||
struct sub_bitmap *parts;
|
||||
struct sub_bitmaps part_cache;
|
||||
char last_text[500];
|
||||
struct mp_image_params video_params;
|
||||
struct mp_image_params last_params;
|
||||
@@ -117,10 +117,10 @@ static void add_subtitle_fonts(struct sd *sd)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
struct MPOpts *opts = sd->opts;
|
||||
if (!opts->ass_enabled || !sd->demuxer)
|
||||
if (!opts->ass_enabled || !sd->attachments)
|
||||
return;
|
||||
for (int i = 0; i < sd->demuxer->num_attachments; i++) {
|
||||
struct demux_attachment *f = &sd->demuxer->attachments[i];
|
||||
for (int i = 0; i < sd->attachments->num_entries; i++) {
|
||||
struct demux_attachment *f = &sd->attachments->entries[i];
|
||||
if (opts->use_embedded_fonts && attachment_is_font(sd->log, f))
|
||||
ass_add_font(ctx->ass_library, f->name, f->data, f->data_size);
|
||||
}
|
||||
@@ -238,6 +238,8 @@ static bool check_packet_seen(struct sd *sd, int64_t pos)
|
||||
return false;
|
||||
}
|
||||
|
||||
#define UNKNOWN_DURATION (INT_MAX / 1000)
|
||||
|
||||
static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
@@ -246,13 +248,22 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 &&
|
||||
check_packet_seen(sd, packet->pos))
|
||||
return;
|
||||
if (packet->duration < 0) {
|
||||
if (!ctx->duration_unknown) {
|
||||
MP_WARN(sd, "Subtitle with unknown duration.\n");
|
||||
ctx->duration_unknown = true;
|
||||
}
|
||||
packet->duration = UNKNOWN_DURATION;
|
||||
}
|
||||
char **r = lavc_conv_decode(ctx->converter, packet);
|
||||
for (int n = 0; r && r[n]; n++)
|
||||
ass_process_data(track, r[n], strlen(r[n]));
|
||||
if (ctx->duration_unknown) {
|
||||
for (int n = 0; n < track->n_events - 1; n++) {
|
||||
track->events[n].Duration = track->events[n + 1].Start -
|
||||
track->events[n].Start;
|
||||
if (track->events[n].Duration == UNKNOWN_DURATION * 1000) {
|
||||
track->events[n].Duration = track->events[n + 1].Start -
|
||||
track->events[n].Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -440,14 +451,22 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
|
||||
long long ts = find_timestamp(sd, pts);
|
||||
if (ctx->duration_unknown && pts != MP_NOPTS_VALUE) {
|
||||
mp_ass_flush_old_events(track, ts);
|
||||
ctx->num_seen_packets = 0;
|
||||
sd->preload_ok = false;
|
||||
}
|
||||
|
||||
if (no_ass)
|
||||
fill_plaintext(sd, pts);
|
||||
mp_ass_render_frame(renderer, track, ts, &ctx->parts, res);
|
||||
talloc_steal(ctx, ctx->parts);
|
||||
|
||||
ctx->part_cache.change_id = 0;
|
||||
ctx->part_cache.num_parts = 0;
|
||||
mp_ass_render_frame(renderer, track, ts, &ctx->part_cache);
|
||||
talloc_steal(ctx, ctx->part_cache.parts);
|
||||
|
||||
if (!converted)
|
||||
mangle_colors(sd, res);
|
||||
mangle_colors(sd, &ctx->part_cache);
|
||||
|
||||
*res = ctx->part_cache;
|
||||
}
|
||||
|
||||
struct buf {
|
||||
@@ -597,9 +616,10 @@ static void fill_plaintext(struct sd *sd, double pts)
|
||||
static void reset(struct sd *sd)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
if (sd->opts->sub_clear_on_seek) {
|
||||
if (sd->opts->sub_clear_on_seek || ctx->duration_unknown) {
|
||||
ass_flush_events(ctx->ass_track);
|
||||
ctx->num_seen_packets = 0;
|
||||
sd->preload_ok = false;
|
||||
}
|
||||
if (ctx->converter)
|
||||
lavc_conv_reset(ctx->converter);
|
||||
|
||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user