Initial import
This commit is contained in:
commit
d389fbd6d5
5 changed files with 406 additions and 0 deletions
112
.clang-format
Normal file
112
.clang-format
Normal file
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
|
||||
ForEachMacros:
|
||||
- 'for_each'
|
||||
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
# Taken from git's rules
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatementsExceptForEachMacros
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 4
|
||||
UseTab: Always
|
||||
...
|
2
.clangd
Normal file
2
.clangd
Normal file
|
@ -0,0 +1,2 @@
|
|||
CompileFlags:
|
||||
CompilationDatabase: build
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
25
src/CMakeLists.txt
Normal file
25
src/CMakeLists.txt
Normal file
|
@ -0,0 +1,25 @@
|
|||
cmake_minimum_required(VERSION 3.21)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
project(lazier-player)
|
||||
|
||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(mpv REQUIRED IMPORTED_TARGET mpv)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
add_compile_options(-fsanitize=address)
|
||||
add_link_options(-fsanitize=address)
|
||||
endif()
|
||||
|
||||
add_executable(
|
||||
lazier-player
|
||||
main.c
|
||||
)
|
||||
|
||||
target_link_libraries(lazier-player PRIVATE SDL3::SDL3 mpv m)
|
||||
set_property(TARGET lazier-player PROPERTY C_STANDARD 23)
|
||||
target_compile_options(lazier-player PRIVATE -Wall -Wextra -Wnull-dereference)
|
||||
target_include_directories(lazier-player PRIVATE include)
|
||||
install(TARGETS lazier-player DESTINATION bin)
|
266
src/main.c
Normal file
266
src/main.c
Normal file
|
@ -0,0 +1,266 @@
|
|||
#include <SDL3/SDL.h>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/render.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
mpv_handle *mpv;
|
||||
mpv_render_context *render_ctx;
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
int width;
|
||||
int height;
|
||||
int running;
|
||||
int needs_redraw;
|
||||
} App;
|
||||
|
||||
static void SDL_PrintError(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "sdl: %s: %s\n", msg, SDL_GetError());
|
||||
}
|
||||
|
||||
static void mpv_print_error(int code, const char *msg)
|
||||
{
|
||||
fprintf(stderr, "mpv: %s: %s\n", msg, mpv_error_string(code));
|
||||
}
|
||||
|
||||
static void handle_mpv_events(App *app)
|
||||
{
|
||||
while (true) {
|
||||
mpv_event *event = mpv_wait_event(app->mpv, 0);
|
||||
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
|
||||
printf("mpv: event=%s\n", mpv_event_name(event->event_id));
|
||||
|
||||
if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) {
|
||||
mpv_event_property *prop = event->data;
|
||||
|
||||
if (!strcmp(prop->name, "width") || !strcmp(prop->name, "height")) {
|
||||
int64_t width = 0;
|
||||
int64_t height = 0;
|
||||
|
||||
mpv_get_property(app->mpv, "width", MPV_FORMAT_INT64, &width);
|
||||
mpv_get_property(app->mpv, "height", MPV_FORMAT_INT64, &height);
|
||||
|
||||
if (width > 0 && height > 0 && (width != app->width || height != app->height)) {
|
||||
app->width = width;
|
||||
app->height = height;
|
||||
|
||||
if (app->texture)
|
||||
SDL_DestroyTexture(app->texture);
|
||||
|
||||
app->texture = SDL_CreateTexture(app->renderer, SDL_PIXELFORMAT_XBGR8888,
|
||||
SDL_TEXTUREACCESS_STREAMING, width, height);
|
||||
|
||||
if (!app->texture) {
|
||||
SDL_PrintError("SDL_CreateTexture");
|
||||
} else {
|
||||
// Resize window to match video dimensions
|
||||
SDL_SetWindowSize(app->window, app->width, app->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event->event_id == MPV_EVENT_END_FILE) {
|
||||
app->running = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void render_video(App *app)
|
||||
{
|
||||
int err;
|
||||
|
||||
// Clear the renderer
|
||||
if (!SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 255))
|
||||
SDL_PrintError("SDL_SetRenderDrawColor");
|
||||
|
||||
if (!SDL_RenderClear(app->renderer))
|
||||
SDL_PrintError("SDL_RenderClear");
|
||||
|
||||
// Render MPV frame
|
||||
if (app->texture && app->render_ctx) {
|
||||
void *pixels;
|
||||
int stride = 0;
|
||||
|
||||
if (!SDL_LockTexture(app->texture, NULL, &pixels, &stride)) {
|
||||
SDL_PrintError("SDL_LockTexture");
|
||||
return;
|
||||
}
|
||||
|
||||
float w, h;
|
||||
if (!SDL_GetTextureSize(app->texture, &w, &h))
|
||||
SDL_PrintError("SDL_GetTextureSize");
|
||||
|
||||
int size[] = { w, h };
|
||||
|
||||
mpv_render_param params[] = {
|
||||
{ MPV_RENDER_PARAM_SW_SIZE, &size }, //
|
||||
{ MPV_RENDER_PARAM_SW_FORMAT, "rgb0" }, //
|
||||
{ MPV_RENDER_PARAM_SW_STRIDE, &stride }, //
|
||||
{ MPV_RENDER_PARAM_SW_POINTER, pixels }, //
|
||||
{ MPV_RENDER_PARAM_INVALID, NULL },
|
||||
};
|
||||
|
||||
if ((err = mpv_render_context_render(app->render_ctx, params)))
|
||||
mpv_print_error(err, "mpv_render_context_render");
|
||||
|
||||
SDL_UnlockTexture(app->texture);
|
||||
|
||||
if (!SDL_RenderTexture(app->renderer, app->texture, NULL, NULL)) {
|
||||
SDL_PrintError("SDL_RenderTexture");
|
||||
}
|
||||
}
|
||||
|
||||
// Draw shapes over the video
|
||||
// 1. Red rectangle
|
||||
if (!SDL_SetRenderDrawColor(app->renderer, 255, 0, 0, 128))
|
||||
SDL_PrintError("SDL_SetRenderDrawColor");
|
||||
|
||||
SDL_FRect rect = { 50, 50, 100, 80 };
|
||||
|
||||
if (!SDL_RenderFillRect(app->renderer, &rect))
|
||||
SDL_PrintError("SDL_RenderFillRect");
|
||||
|
||||
// 2. Blue circle (approximated with points)
|
||||
if (!SDL_SetRenderDrawColor(app->renderer, 0, 0, 255, 128))
|
||||
SDL_PrintError("SDL_SetRenderDrawColor");
|
||||
|
||||
int centerX = app->width - 100;
|
||||
int centerY = app->height - 100;
|
||||
int radius = 40;
|
||||
|
||||
for (int i = 0; i < 360; i++) {
|
||||
float angle = i * 3.14159f / 180.0f;
|
||||
float x = centerX + radius * SDL_cosf(angle);
|
||||
float y = centerY + radius * SDL_sinf(angle);
|
||||
|
||||
if (!SDL_RenderPoint(app->renderer, x, y))
|
||||
SDL_PrintError("SDL_RenderPoint");
|
||||
}
|
||||
|
||||
// 3. Green line
|
||||
if (!SDL_SetRenderDrawColor(app->renderer, 0, 255, 0, 200))
|
||||
SDL_PrintError("SDL_SetRenderDrawColor");
|
||||
|
||||
if (!SDL_RenderLine(app->renderer, 0, 0, app->width, app->height))
|
||||
SDL_PrintError("SDL_RenderLine");
|
||||
|
||||
if (!SDL_RenderPresent(app->renderer))
|
||||
SDL_PrintError("SDL_RenderPresent");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int err;
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <video_file>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static App app = { 0 };
|
||||
app.running = 1;
|
||||
app.width = 800;
|
||||
app.height = 600;
|
||||
|
||||
// Initialize SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
SDL_PrintError("SDL_Init");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create window
|
||||
app.window = SDL_CreateWindow("SDL3 + MPV Video Player", app.width, app.height, 0);
|
||||
if (!app.window) {
|
||||
SDL_PrintError("SDL_CreateWindow");
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create renderer
|
||||
app.renderer = SDL_CreateRenderer(app.window, "software");
|
||||
if (!app.renderer) {
|
||||
SDL_PrintError("SDL_CreateRenderer");
|
||||
SDL_DestroyWindow(app.window);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize MPV
|
||||
app.mpv = mpv_create();
|
||||
if (!app.mpv) {
|
||||
fprintf(stderr, "mpv: mpv_create: returned NULL\n");
|
||||
SDL_DestroyRenderer(app.renderer);
|
||||
SDL_DestroyWindow(app.window);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Configure MPV
|
||||
if ((err = mpv_set_option_string(app.mpv, "video-sync", "display-resample")))
|
||||
mpv_print_error(err, "mpv_set_option_string");
|
||||
|
||||
if ((err = mpv_set_option_string(app.mpv, "vo", "libmpv")))
|
||||
mpv_print_error(err, "mpv_set_option_string");
|
||||
|
||||
if ((err = mpv_initialize(app.mpv)))
|
||||
mpv_print_error(err, "mpv_initialize");
|
||||
|
||||
// Initialize MPV render context
|
||||
mpv_render_param params[] = {
|
||||
{ MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_SW },
|
||||
{ MPV_RENDER_PARAM_INVALID, NULL },
|
||||
};
|
||||
|
||||
if ((err = mpv_render_context_create(&app.render_ctx, app.mpv, params)))
|
||||
mpv_print_error(err, "mpv_render_context_create");
|
||||
|
||||
// Load the video file
|
||||
const char *cmd[] = { "loadfile", argv[1], NULL };
|
||||
|
||||
if ((err = mpv_command(app.mpv, cmd)))
|
||||
mpv_print_error(err, "mpv_command");
|
||||
|
||||
// Observe properties
|
||||
if ((err = mpv_observe_property(app.mpv, 0, "width", MPV_FORMAT_INT64)))
|
||||
mpv_print_error(err, "mpv_observe_property");
|
||||
|
||||
if ((err = mpv_observe_property(app.mpv, 0, "height", MPV_FORMAT_INT64)))
|
||||
mpv_print_error(err, "mpv_observe_property");
|
||||
|
||||
// Main loop
|
||||
while (app.running) {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
app.running = 0;
|
||||
}
|
||||
}
|
||||
|
||||
handle_mpv_events(&app);
|
||||
render_video(&app);
|
||||
|
||||
SDL_Delay(16); // ~60fps
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (app.texture)
|
||||
SDL_DestroyTexture(app.texture);
|
||||
|
||||
if (app.render_ctx)
|
||||
mpv_render_context_free(app.render_ctx);
|
||||
|
||||
mpv_terminate_destroy(app.mpv);
|
||||
SDL_DestroyRenderer(app.renderer);
|
||||
SDL_DestroyWindow(app.window);
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// vim:set ts=4 sw=4 et:
|
Loading…
Reference in a new issue