kino/src/player.c

950 lines
26 KiB
C

#include "player.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <glib.h>
struct Player {
mpv_handle *mpv;
unsigned long wid;
PlayerEventCallback event_callback;
gpointer event_callback_data;
gboolean initialized;
char *config_path;
};
static void check_error(int status)
{
if (status < 0) {
fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
}
}
Player* player_new(void)
{
if (load_mpv_library() < 0) {
return NULL;
}
Player *player = g_new0(Player, 1);
player->mpv = mpv_create();
if (!player->mpv) {
fprintf(stderr, "Failed to create mpv context\n");
g_free(player);
return NULL;
}
player->wid = 0;
player->event_callback = NULL;
player->event_callback_data = NULL;
player->initialized = FALSE;
return player;
}
void player_destroy(Player *player)
{
if (!player) return;
if (player->mpv) {
mpv_terminate_destroy(player->mpv);
}
g_free(player->config_path);
g_free(player);
}
void player_set_window(Player *player, unsigned long wid)
{
if (!player) return;
player->wid = wid;
}
void player_set_config_path(Player *player, const char *path)
{
if (!player) return;
g_free(player->config_path);
player->config_path = path ? g_strdup(path) : NULL;
}
static void wakeup_callback(void *ctx)
{
/* This is called from mpv's thread. We need to wake up the GTK main loop. */
g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)player_process_events, ctx, NULL);
}
static void load_scripts(mpv_handle *mpv)
{
const char *config_dir = g_get_user_config_dir();
char *scripts_dir = g_build_filename(config_dir, "gtk2-media-player", "scripts", NULL);
printf("Scanning for scripts in: %s\n", scripts_dir);
/* Create scripts directory if it doesn't exist */
g_mkdir_with_parents(scripts_dir, 0755);
DIR *dir = opendir(scripts_dir);
if (!dir) {
printf("Failed to open scripts directory!\n");
} else {
struct dirent *entry;
int count = 0;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
char *path = g_build_filename(scripts_dir, entry->d_name, NULL);
size_t len = strlen(entry->d_name);
/* Check extension (Lua or JS) */
if ((len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0) ||
(len > 3 && strcmp(entry->d_name + len - 3, ".js") == 0)) {
printf("Loading script: %s\n", path);
/* Use load-script command (works after mpv_initialize) */
const char *cmd[] = {"load-script", path, NULL};
int res = mpv_command(mpv, cmd);
if (res < 0) {
printf(" -> Failed! Error: %s\n", mpv_error_string(res));
} else {
printf(" -> OK\n");
count++;
}
}
g_free(path);
}
closedir(dir);
printf("Loaded %d script(s)\n", count);
}
g_free(scripts_dir);
}
int player_init(Player *player)
{
if (!player || !player->mpv) return -1;
/* Set window ID if provided - Must be before initialize */
if (player->wid != 0) {
char wid_str[64];
snprintf(wid_str, sizeof(wid_str), "%lu", player->wid);
check_error(mpv_set_option_string(player->mpv, "wid", wid_str));
}
/* Basic configuration - Set BEFORE config loading so config can override defaults */
check_error(mpv_set_option_string(player->mpv, "input-default-bindings", "no"));
check_error(mpv_set_option_string(player->mpv, "input-vo-keyboard", "no"));
check_error(mpv_set_option_string(player->mpv, "osc", "no")); /* We have our own controls */
check_error(mpv_set_option_string(player->mpv, "osd-bar", "yes"));
check_error(mpv_set_option_string(player->mpv, "osd-duration", "2000"));
check_error(mpv_set_option_string(player->mpv, "input-cursor", "no")); /* Let GTK handle the cursor */
check_error(mpv_set_option_string(player->mpv, "keep-open", "yes"));
check_error(mpv_set_option_string(player->mpv, "idle", "yes"));
/* Load external config file if provided */
if (player->config_path && g_file_test(player->config_path, G_FILE_TEST_EXISTS)) {
check_error(mpv_set_option_string(player->mpv, "config", "yes"));
check_error(mpv_set_option_string(player->mpv, "config-file", player->config_path));
}
/* Initialize MPV */
int res = mpv_initialize(player->mpv);
if (res < 0) {
check_error(res);
return -1;
}
/* Load scripts AFTER initialization using load-script command */
load_scripts(player->mpv);
/* Set wakeup callback for event processing */
mpv_set_wakeup_callback(player->mpv, wakeup_callback, player);
player->initialized = TRUE;
return 0;
}
/* Playback commands */
void player_load_file(Player *player, const char *path, gboolean append)
{
if (!player || !player->mpv || !path) return;
const char *cmd[] = {"loadfile", path, append ? "append-play" : "replace", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_play(Player *player)
{
if (!player || !player->mpv) return;
int flag = 0;
mpv_set_property_async(player->mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
}
void player_pause(Player *player)
{
if (!player || !player->mpv) return;
int flag = 1;
mpv_set_property_async(player->mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
}
void player_toggle_pause(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"cycle", "pause", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_stop(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"stop", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_seek(Player *player, double seconds)
{
if (!player || !player->mpv) return;
char sec_str[32];
snprintf(sec_str, sizeof(sec_str), "%f", seconds);
const char *cmd[] = {"seek", sec_str, "relative", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
/* Show progress OSD */
const char *osd_cmd[] = {"show-progress", NULL};
mpv_command_async(player->mpv, 0, osd_cmd);
}
void player_seek_absolute(Player *player, double position)
{
if (!player || !player->mpv) return;
char pos_str[32];
snprintf(pos_str, sizeof(pos_str), "%f", position);
const char *cmd[] = {"seek", pos_str, "absolute+exact", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
/* Show progress OSD */
const char *osd_cmd[] = {"show-progress", NULL};
mpv_command_async(player->mpv, 0, osd_cmd);
}
void player_frame_step(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"frame-step", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_frame_back_step(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"frame-back-step", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_set_option_string(Player *player, const char *name, const char *value)
{
if (!player || !player->mpv) return;
if (player->initialized) {
check_error(mpv_set_property_string(player->mpv, name, value));
} else {
check_error(mpv_set_option_string(player->mpv, name, value));
}
}
void player_set_option_flag(Player *player, const char *name, int value)
{
if (!player || !player->mpv) return;
if (player->initialized) {
const char *val = value ? "yes" : "no";
check_error(mpv_set_property_string(player->mpv, name, val));
} else {
check_error(mpv_set_option_string(player->mpv, name, value ? "yes" : "no"));
}
}
/* Property getters */
double player_get_duration(Player *player)
{
if (!player || !player->mpv) return 0.0;
double duration = 0.0;
mpv_get_property(player->mpv, "duration", MPV_FORMAT_DOUBLE, &duration);
return duration;
}
double player_get_position(Player *player)
{
if (!player || !player->mpv) return 0.0;
double position = 0.0;
mpv_get_property(player->mpv, "time-pos", MPV_FORMAT_DOUBLE, &position);
return position;
}
double player_get_volume(Player *player)
{
if (!player || !player->mpv) return 100.0;
double volume = 100.0;
mpv_get_property(player->mpv, "volume", MPV_FORMAT_DOUBLE, &volume);
return volume;
}
gboolean player_get_paused(Player *player)
{
if (!player || !player->mpv) return TRUE;
int paused = 1;
mpv_get_property(player->mpv, "pause", MPV_FORMAT_FLAG, &paused);
return paused ? TRUE : FALSE;
}
gboolean player_get_muted(Player *player)
{
if (!player || !player->mpv) return FALSE;
int muted = 0;
mpv_get_property(player->mpv, "mute", MPV_FORMAT_FLAG, &muted);
return muted ? TRUE : FALSE;
}
double player_get_speed(Player *player)
{
if (!player || !player->mpv) return 1.0;
double speed = 1.0;
mpv_get_property(player->mpv, "speed", MPV_FORMAT_DOUBLE, &speed);
return speed;
}
char* player_get_media_title(Player *player)
{
if (!player || !player->mpv) return NULL;
char *title = NULL;
mpv_get_property(player->mpv, "media-title", MPV_FORMAT_STRING, &title);
if (title) {
char *result = g_strdup(title);
mpv_free(title);
return result;
}
return NULL;
}
char* player_get_filename(Player *player)
{
if (!player || !player->mpv) return NULL;
char *filename = NULL;
mpv_get_property(player->mpv, "filename", MPV_FORMAT_STRING, &filename);
if (filename) {
char *result = g_strdup(filename);
mpv_free(filename);
return result;
}
return NULL;
}
gboolean player_get_eof_reached(Player *player)
{
if (!player || !player->mpv) return FALSE;
int flag = 0;
int status = mpv_get_property(player->mpv, "eof-reached", MPV_FORMAT_FLAG, &flag);
if (status < 0) {
return FALSE;
}
return flag ? TRUE : FALSE;
}
gboolean player_is_idle(Player *player)
{
if (!player || !player->mpv) return TRUE;
int idle = 1;
if (mpv_get_property(player->mpv, "idle-active", MPV_FORMAT_FLAG, &idle) < 0) {
return TRUE;
}
/* Double check: if it says not idle, ensure we have a file loaded */
if (!idle) {
char *path = NULL;
if (mpv_get_property(player->mpv, "path", MPV_FORMAT_STRING, &path) < 0 || !path) {
return TRUE;
}
if (path) mpv_free(path);
}
return idle ? TRUE : FALSE;
}
char* player_get_metadata_tag(Player *player, const char *tag)
{
if (!player || !player->mpv || !tag) return NULL;
char prop[128];
snprintf(prop, sizeof(prop), "metadata/by-key/%s", tag);
char *value = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &value);
if (value) {
char *result = g_strdup(value);
mpv_free(value);
return result;
}
return NULL;
}
gboolean player_get_shuffle(Player *player)
{
if (!player || !player->mpv) return FALSE;
int flag = 0;
mpv_get_property(player->mpv, "shuffle", MPV_FORMAT_FLAG, &flag);
return flag ? TRUE : FALSE;
}
gboolean player_get_loop(Player *player)
{
if (!player || !player->mpv) return FALSE;
char *value = NULL;
mpv_get_property(player->mpv, "loop-playlist", MPV_FORMAT_STRING, &value);
gboolean result = FALSE;
if (value) {
if (strcmp(value, "no") != 0) result = TRUE;
mpv_free(value);
}
return result;
}
/* Property setters */
void player_set_volume(Player *player, double volume)
{
if (!player || !player->mpv) return;
char vol_str[32];
snprintf(vol_str, sizeof(vol_str), "%f", volume);
if (player->initialized) {
check_error(mpv_set_property_string(player->mpv, "volume", vol_str));
/* Show volume bar on OSD */
const char *cmd[] = {"osd-msg-bar", "set", "volume", NULL};
mpv_command_async(player->mpv, 0, cmd);
} else {
check_error(mpv_set_option_string(player->mpv, "volume", vol_str));
}
}
void player_set_muted(Player *player, gboolean muted)
{
if (!player || !player->mpv) return;
if (player->initialized) {
const char *val = muted ? "yes" : "no";
check_error(mpv_set_property_string(player->mpv, "mute", val));
} else {
check_error(mpv_set_option_string(player->mpv, "mute", muted ? "yes" : "no"));
}
}
void player_set_speed(Player *player, double speed)
{
if (!player || !player->mpv) return;
char speed_str[32];
snprintf(speed_str, sizeof(speed_str), "%f", speed);
check_error(mpv_set_property_string(player->mpv, "speed", speed_str));
}
/* Track management */
int player_get_audio_track_count(Player *player)
{
if (!player || !player->mpv) return 0;
int64_t count = 0;
mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &count);
/* Count only audio tracks */
int audio_count = 0;
for (int i = 0; i < count; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "audio") == 0) {
audio_count++;
}
if (type) mpv_free(type);
}
return audio_count;
}
int player_get_subtitle_track_count(Player *player)
{
if (!player || !player->mpv) return 0;
int64_t count = 0;
mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &count);
/* Count only subtitle tracks */
int sub_count = 0;
for (int i = 0; i < count; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "sub") == 0) {
sub_count++;
}
if (type) mpv_free(type);
}
return sub_count;
}
int player_get_current_audio_track(Player *player)
{
if (!player || !player->mpv) return 0;
int64_t aid = 0;
mpv_get_property(player->mpv, "aid", MPV_FORMAT_INT64, &aid);
return (int)aid;
}
int player_get_current_subtitle_track(Player *player)
{
if (!player || !player->mpv) return 0;
int64_t sid = 0;
mpv_get_property(player->mpv, "sid", MPV_FORMAT_INT64, &sid);
return (int)sid;
}
void player_set_audio_track(Player *player, int track)
{
if (!player || !player->mpv) return;
char aid_str[16];
snprintf(aid_str, sizeof(aid_str), "%d", track);
check_error(mpv_set_property_string(player->mpv, "aid", aid_str));
}
void player_set_subtitle_track(Player *player, int track)
{
if (!player || !player->mpv) return;
char sid_str[16];
snprintf(sid_str, sizeof(sid_str), "%d", track);
check_error(mpv_set_property_string(player->mpv, "sid", sid_str));
}
void player_load_subtitle(Player *player, const char *path)
{
if (!player || !player->mpv || !path) return;
const char *cmd[] = {"sub-add", path, NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
/* Track list helpers */
char** player_get_audio_track_list(Player *player, int *count)
{
if (!player || !player->mpv || !count) return NULL;
int64_t total = 0;
mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &total);
/* First pass: count audio tracks */
int audio_count = 0;
for (int i = 0; i < total; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "audio") == 0) {
audio_count++;
}
if (type) mpv_free(type);
}
*count = audio_count;
if (audio_count == 0) return NULL;
char **list = g_new0(char*, audio_count);
int idx = 0;
for (int i = 0; i < total && idx < audio_count; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "audio") == 0) {
/* Get track title or lang */
char title_prop[64], lang_prop[64], id_prop[64];
snprintf(title_prop, sizeof(title_prop), "track-list/%d/title", i);
snprintf(lang_prop, sizeof(lang_prop), "track-list/%d/lang", i);
snprintf(id_prop, sizeof(id_prop), "track-list/%d/id", i);
char *title = NULL, *lang = NULL;
int64_t id = 0;
mpv_get_property(player->mpv, title_prop, MPV_FORMAT_STRING, &title);
mpv_get_property(player->mpv, lang_prop, MPV_FORMAT_STRING, &lang);
mpv_get_property(player->mpv, id_prop, MPV_FORMAT_INT64, &id);
if (title) {
list[idx] = g_strdup_printf("Track %ld: %s", (long)id, title);
} else if (lang) {
list[idx] = g_strdup_printf("Track %ld: %s", (long)id, lang);
} else {
list[idx] = g_strdup_printf("Track %ld", (long)id);
}
if (title) mpv_free(title);
if (lang) mpv_free(lang);
idx++;
}
if (type) mpv_free(type);
}
return list;
}
void player_get_video_resolution(Player *player, int *w, int *h)
{
if (!player || !player->mpv) {
if (w) *w = 0;
if (h) *h = 0;
return;
}
int64_t width = 0, height = 0;
mpv_get_property(player->mpv, "dwidth", MPV_FORMAT_INT64, &width);
mpv_get_property(player->mpv, "dheight", MPV_FORMAT_INT64, &height);
if (w) *w = (int)width;
if (h) *h = (int)height;
}
char** player_get_subtitle_track_list(Player *player, int *count)
{
if (!player || !player->mpv || !count) return NULL;
int64_t total = 0;
mpv_get_property(player->mpv, "track-list/count", MPV_FORMAT_INT64, &total);
/* First pass: count subtitle tracks */
int sub_count = 0;
for (int i = 0; i < total; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "sub") == 0) {
sub_count++;
}
if (type) mpv_free(type);
}
*count = sub_count;
if (sub_count == 0) return NULL;
char **list = g_new0(char*, sub_count);
int idx = 0;
for (int i = 0; i < total && idx < sub_count; i++) {
char prop[64];
snprintf(prop, sizeof(prop), "track-list/%d/type", i);
char *type = NULL;
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &type);
if (type && strcmp(type, "sub") == 0) {
char title_prop[64], lang_prop[64], id_prop[64];
snprintf(title_prop, sizeof(title_prop), "track-list/%d/title", i);
snprintf(lang_prop, sizeof(lang_prop), "track-list/%d/lang", i);
snprintf(id_prop, sizeof(id_prop), "track-list/%d/id", i);
char *title = NULL, *lang = NULL;
int64_t id = 0;
mpv_get_property(player->mpv, title_prop, MPV_FORMAT_STRING, &title);
mpv_get_property(player->mpv, lang_prop, MPV_FORMAT_STRING, &lang);
mpv_get_property(player->mpv, id_prop, MPV_FORMAT_INT64, &id);
if (title) {
list[idx] = g_strdup_printf("Track %ld: %s", (long)id, title);
} else if (lang) {
list[idx] = g_strdup_printf("Track %ld: %s", (long)id, lang);
} else {
list[idx] = g_strdup_printf("Track %ld", (long)id);
}
if (title) mpv_free(title);
if (lang) mpv_free(lang);
idx++;
}
if (type) mpv_free(type);
}
return list;
}
void player_free_track_list(char **list, int count)
{
if (!list) return;
for (int i = 0; i < count; i++) {
g_free(list[i]);
}
g_free(list);
}
/* Playlist */
int player_get_playlist_count(Player *player)
{
if (!player || !player->mpv) return 0;
int64_t count = 0;
mpv_get_property(player->mpv, "playlist-count", MPV_FORMAT_INT64, &count);
return (int)count;
}
int player_get_playlist_pos(Player *player)
{
if (!player || !player->mpv) return -1;
int64_t pos = -1;
mpv_get_property(player->mpv, "playlist-pos", MPV_FORMAT_INT64, &pos);
return (int)pos;
}
void player_playlist_next(Player *player)
{
if (!player || !player->mpv) return;
if (player_get_playlist_count(player) == 1) {
player_seek_absolute(player, 0);
} else {
const char *cmd[] = {"playlist-next", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
}
void player_playlist_prev(Player *player)
{
if (!player || !player->mpv) return;
if (player_get_playlist_count(player) == 1) {
player_seek_absolute(player, 0);
} else {
const char *cmd[] = {"playlist-prev", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
}
void player_playlist_play_index(Player *player, int index)
{
if (!player || !player->mpv) return;
char idx_str[32];
snprintf(idx_str, sizeof(idx_str), "%d", index);
const char *cmd[] = {"playlist-play-index", idx_str, NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_playlist_remove(Player *player, int index)
{
if (!player || !player->mpv) return;
char idx_str[32];
snprintf(idx_str, sizeof(idx_str), "%d", index);
const char *cmd[] = {"playlist-remove", idx_str, NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_playlist_move(Player *player, int from, int to)
{
if (!player || !player->mpv) return;
char from_str[32], to_str[32];
snprintf(from_str, sizeof(from_str), "%d", from);
snprintf(to_str, sizeof(to_str), "%d", to);
const char *cmd[] = {"playlist-move", from_str, to_str, NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_playlist_clear(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"playlist-clear", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_playlist_shuffle(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"playlist-shuffle", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_set_shuffle(Player *player, gboolean shuffle)
{
if (!player || !player->mpv) return;
int val = shuffle ? 1 : 0;
check_error(mpv_set_property_async(player->mpv, 0, "shuffle", MPV_FORMAT_FLAG, &val));
}
void player_set_loop(Player *player, gboolean loop)
{
if (!player || !player->mpv) return;
const char *val = loop ? "inf" : "no";
check_error(mpv_set_property_async(player->mpv, 0, "loop-playlist", MPV_FORMAT_STRING, &val));
}
/* A-B Loop */
void player_set_ab_loop_a(Player *player)
{
if (!player || !player->mpv) return;
double pos = player_get_position(player);
mpv_set_property_async(player->mpv, 0, "ab-loop-a", MPV_FORMAT_DOUBLE, &pos);
}
void player_set_ab_loop_b(Player *player)
{
if (!player || !player->mpv) return;
double pos = player_get_position(player);
mpv_set_property_async(player->mpv, 0, "ab-loop-b", MPV_FORMAT_DOUBLE, &pos);
}
void player_clear_ab_loop(Player *player)
{
if (!player || !player->mpv) return;
/* Reset by setting to "no" */
mpv_set_property_string(player->mpv, "ab-loop-a", "no");
mpv_set_property_string(player->mpv, "ab-loop-b", "no");
}
double player_get_ab_loop_a(Player *player)
{
if (!player || !player->mpv) return -1.0;
double a = -1.0;
mpv_get_property(player->mpv, "ab-loop-a", MPV_FORMAT_DOUBLE, &a);
return a;
}
double player_get_ab_loop_b(Player *player)
{
if (!player || !player->mpv) return -1.0;
double b = -1.0;
mpv_get_property(player->mpv, "ab-loop-b", MPV_FORMAT_DOUBLE, &b);
return b;
}
/* Screenshot */
void player_screenshot(Player *player)
{
if (!player || !player->mpv) return;
const char *cmd[] = {"screenshot", NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
void player_screenshot_to_file(Player *player, const char *path)
{
if (!player || !player->mpv || !path) return;
const char *cmd[] = {"screenshot-to-file", path, NULL};
check_error(mpv_command_async(player->mpv, 0, cmd));
}
/* Aspect ratio */
void player_set_aspect_ratio(Player *player, const char *aspect)
{
if (!player || !player->mpv) return;
mpv_set_property_string(player->mpv, "video-aspect-override", aspect ? aspect : "-1");
}
/* Event handling */
void player_set_event_callback(Player *player, PlayerEventCallback callback, gpointer user_data)
{
if (!player) return;
player->event_callback = callback;
player->event_callback_data = user_data;
}
gboolean player_process_events(gpointer data)
{
Player *player = (Player *)data;
if (!player || !player->mpv) return G_SOURCE_REMOVE;
while (1) {
mpv_event *event = mpv_wait_event(player->mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
if (player->event_callback) {
player->event_callback(player, event, player->event_callback_data);
}
if (event->event_id == MPV_EVENT_SHUTDOWN) {
break;
}
}
return G_SOURCE_REMOVE;
}
mpv_handle* player_get_mpv_handle(Player *player)
{
if (!player) return NULL;
return player->mpv;
}
/* Property observation */
void player_observe_property(Player *player, const char *name, mpv_format format, guint64 reply_userdata)
{
if (!player || !player->mpv || !name) return;
check_error(mpv_observe_property(player->mpv, reply_userdata, name, format));
}
void player_unobserve_property(Player *player, guint64 reply_userdata)
{
if (!player || !player->mpv) return;
mpv_unobserve_property(player->mpv, reply_userdata);
}