#include "app.h" #include #include #include static void destroy_video_texture(App *app) { if (app->video_texture) { SDL_DestroyTexture(app->video_texture); app->video_texture = NULL; app->texture_width = 0; app->texture_height = 0; } } static int update_video_texture(App *app) { FrameData frame = {0}; if (!player_consume_latest_frame(&app->player, &frame)) { return 0; } if (!app->video_texture || app->texture_width != frame.width || app->texture_height != frame.height) { destroy_video_texture(app); app->video_texture = SDL_CreateTexture(app->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, frame.width, frame.height); if (!app->video_texture) { frame_data_free(&frame); return -1; } app->texture_width = frame.width; app->texture_height = frame.height; SDL_SetTextureBlendMode(app->video_texture, SDL_BLENDMODE_NONE); } SDL_UpdateTexture(app->video_texture, NULL, frame.pixels, frame.stride); frame_data_free(&frame); return 0; } static void render_black(App *app) { SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 255); SDL_RenderClear(app->renderer); } static void tune_relative(App *app, int delta) { int next_index; if (app->channels.count == 0) { return; } next_index = app->player.current_index; if (next_index < 0) { next_index = 0; } next_index = (next_index + delta + app->channels.count) % app->channels.count; player_tune(&app->player, next_index); } static void handle_event(App *app, const SDL_Event *event) { if (event->type == SDL_QUIT) { app->running = 0; return; } if (event->type != SDL_KEYDOWN || event->key.repeat) { return; } switch (event->key.keysym.sym) { case SDLK_ESCAPE: if (app->mode == MODE_GUIDE) { app->mode = MODE_FULLSCREEN; } else { app->running = 0; } break; case SDLK_TAB: app->mode = app->mode == MODE_FULLSCREEN ? MODE_GUIDE : MODE_FULLSCREEN; break; case SDLK_UP: tune_relative(app, -1); break; case SDLK_DOWN: tune_relative(app, 1); break; default: break; } } int app_init(App *app) { memset(app, 0, sizeof(*app)); app->running = 1; app->mode = MODE_FULLSCREEN; app->app_start_time = time(NULL); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0) { fprintf(stderr, "SDL init failed: %s\n", SDL_GetError()); return -1; } if (TTF_Init() != 0) { fprintf(stderr, "SDL_ttf init failed: %s\n", TTF_GetError()); return -1; } app->window = SDL_CreateWindow("Passport-C Media Player", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); app->renderer = SDL_CreateRenderer(app->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!app->window || !app->renderer) { fprintf(stderr, "SDL window or renderer creation failed: %s\n", SDL_GetError()); return -1; } if (ui_load_fonts(&app->fonts) != 0) { fprintf(stderr, "Unable to load a fallback font.\n"); return -1; } if (channel_list_load(&app->channels, "./media") < 0) { fprintf(stderr, "Unable to scan ./media.\n"); return -1; } if (player_init(&app->player, &app->channels, app->app_start_time) != 0) { fprintf(stderr, "Unable to initialize player.\n"); return -1; } if (app->channels.count > 0) { player_tune(&app->player, 0); } return 0; } void app_run(App *app) { SDL_Event event; while (app->running) { while (SDL_PollEvent(&event)) { handle_event(app, &event); } if (update_video_texture(app) != 0) { app->running = 0; break; } if (app->channels.count == 0) { ui_render_no_media(app->renderer, &app->fonts); } else if (player_is_in_blackout(&app->player)) { render_black(app); } else if (app->mode == MODE_GUIDE) { ui_render_guide(app->renderer, app->video_texture, app->texture_width, app->texture_height, &app->fonts, &app->channels, app->player.current_index, app->app_start_time, time(NULL)); } else { ui_render_fullscreen(app->renderer, app->video_texture, app->texture_width, app->texture_height); } SDL_RenderPresent(app->renderer); } } void app_destroy(App *app) { if (!app) { return; } player_destroy(&app->player); channel_list_destroy(&app->channels); destroy_video_texture(app); ui_destroy_fonts(&app->fonts); if (app->renderer) { SDL_DestroyRenderer(app->renderer); } if (app->window) { SDL_DestroyWindow(app->window); } TTF_Quit(); SDL_Quit(); }