diff --git a/Makefile b/Makefile index dcb5403..22334cc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ CC ?= cc CSTD ?= -std=c11 CFLAGS ?= -O2 -Wall -Wextra -Wpedantic $(CSTD) -CFLAGS += -MMD -MP SDL_CFLAGS := $(shell pkg-config --cflags SDL2 SDL2_ttf SDL2_image 2>/dev/null) SDL_LIBS := $(shell pkg-config --libs SDL2 SDL2_ttf SDL2_image 2>/dev/null) @@ -20,7 +19,6 @@ endif SRC := $(wildcard src/*.c) OBJ := $(SRC:.c=.o) -DEP := $(OBJ:.o=.d) BIN := passport-c-media-player CPPFLAGS += -I./src $(SDL_CFLAGS) $(FFMPEG_CFLAGS) @@ -33,8 +31,6 @@ all: $(BIN) $(BIN): $(OBJ) $(CC) $(OBJ) -o $@ $(LDLIBS) --include $(DEP) - run: $(BIN) ./$(BIN) diff --git a/README.md b/README.md index 776083e..f675a05 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# FreePassport-C Media Player +# Passport-C Media Player -FreePassport-C Media Player is a low-dependency SDL2 + FFmpeg application that is based on the look of interactive programming guide software used on older Cisco/Scientific Atlanta Explorer cable boxes, it allows you to organize your media into a virtual cable guide with live-seeking channels. +Passport-C Media Player is a low-dependency SDL2 + FFmpeg prototype that recreates the 2004-era Passport DCT cable guide with live-seeking channels. ## Features @@ -21,7 +21,6 @@ FreePassport-C Media Player is a low-dependency SDL2 + FFmpeg application that i - `Up` / `Down` - surf channels - `Tab` - toggle guide -- `i` - show current channel info - `Esc` - exit guide or quit the app ## Build @@ -49,17 +48,10 @@ To create a portable AppImage that can run on any Linux distribution: make appimage ``` -This creates a self-contained executable that includes all dependencies. The script automatically detects your architecture and produces the appropriate AppImage: - -- **x86_64**: `Passport-C-Media-Player-0.1-x86_64.AppImage` -- **ARM64/aarch64**: `Passport-C-Media-Player-0.1-aarch64.AppImage` - -Users can run it directly: +This creates `Passport-C-Media-Player-0.1-x86_64.AppImage`, a self-contained executable that includes all dependencies. Users can run it directly: ```bash ./Passport-C-Media-Player-0.1-x86_64.AppImage -# or on ARM devices: -./Passport-C-Media-Player-0.1-aarch64.AppImage ``` For a fully portable setup, create a `media/` directory in the same folder as the AppImage and add your channel videos there. The AppImage will automatically detect and load channels from this directory. diff --git a/build-appimage.sh b/build-appimage.sh index 4dfb307..1d11705 100755 --- a/build-appimage.sh +++ b/build-appimage.sh @@ -2,8 +2,9 @@ set -e -APP_NAME="FreePassport-C-Media-Player" +APP_NAME="Passport-C-Media-Player" APP_VERSION="0.1" +OUTPUT_NAME="${APP_NAME}-${APP_VERSION}-x86_64.AppImage" # Colors for output RED='\033[0;31m' @@ -11,29 +12,9 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color -# Detect architecture -ARCH=$(uname -m) -case $ARCH in - x86_64) - LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - LINUXDEPLOY_BIN="linuxdeploy-x86_64.AppImage" - ;; - aarch64|arm64) - LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-aarch64.AppImage" - LINUXDEPLOY_BIN="linuxdeploy-aarch64.AppImage" - ;; - *) - echo -e "${RED}Error: Unsupported architecture: $ARCH${NC}" - echo -e "${RED}Supported architectures: x86_64, aarch64${NC}" - exit 1 - ;; -esac - -OUTPUT_NAME="${APP_NAME}-${APP_VERSION}-${ARCH}.AppImage" - rm -rf AppDir/ -echo -e "${GREEN}Building ${APP_NAME} AppImage for ${ARCH}...${NC}" +echo -e "${GREEN}Building ${APP_NAME} AppImage...${NC}" # Check if binary exists if [ ! -f "passport-c-media-player" ]; then @@ -52,18 +33,10 @@ mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps cp passport-c-media-player AppDir/usr/bin/ chmod +x AppDir/usr/bin/passport-c-media-player -# Copy bundled resources (font and logo for AppImage) -if [ -f "BigBlueTermPlusNerdFontMono-Regular.ttf" ]; then - cp BigBlueTermPlusNerdFontMono-Regular.ttf AppDir/usr/bin/ -fi -if [ -f "logo.png" ]; then - cp logo.png AppDir/usr/bin/ -fi - # Create desktop file directly in script (avoids copy conflicts) cat > AppDir/passport-c-media-player.desktop << 'EOF' [Desktop Entry] -Name=FreePassport-C Media Player +Name=Passport-C Media Player Comment=Turn your media library into an early 2000s cable box TV guide Exec=passport-c-media-player Icon=passport-c-media-player @@ -90,15 +63,15 @@ ln -sf usr/bin/passport-c-media-player AppRun cd .. # Download linuxdeploy if not present -if [ ! -f "${LINUXDEPLOY_BIN}" ]; then - echo -e "${GREEN}Downloading linuxdeploy for ${ARCH}...${NC}" - wget -q "${LINUXDEPLOY_URL}" -O "${LINUXDEPLOY_BIN}" - chmod +x "${LINUXDEPLOY_BIN}" +if [ ! -f "linuxdeploy-x86_64.AppImage" ]; then + echo -e "${GREEN}Downloading linuxdeploy...${NC}" + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage fi # Build AppImage -echo -e "${GREEN}Creating AppImage for ${ARCH}...${NC}" -./"${LINUXDEPLOY_BIN}" \ +echo -e "${GREEN}Creating AppImage...${NC}" +./linuxdeploy-x86_64.AppImage \ --appdir AppDir \ --output appimage \ -d AppDir/usr/share/applications/passport-c-media-player.desktop \ @@ -106,7 +79,7 @@ echo -e "${GREEN}Creating AppImage for ${ARCH}...${NC}" # Rename output file # Use wildcard to find whatever AppImage was created (handles underscore/dash variations) -for file in FreePassport-C_Media_Player-*.AppImage; do +for file in Passport*.AppImage; do if [ -f "$file" ]; then mv "$file" "${OUTPUT_NAME}" break diff --git a/screenshotguide.png b/screenshotguide.png new file mode 100644 index 0000000..4ed963b Binary files /dev/null and b/screenshotguide.png differ diff --git a/src/app.c b/src/app.c index 5075bb4..c67c289 100644 --- a/src/app.c +++ b/src/app.c @@ -15,7 +15,6 @@ #define CHANNEL_BANNER_DURATION_MS 5000 #define CHANNEL_SWITCH_LOCK_DURATION_MS 7000 #define NUMERIC_INPUT_TIMEOUT_MS 1000 -#define GUIDE_PREVIEW_FRAME_INTERVAL_MS 83 static void configure_runtime_environment(void) { char runtime_dir[64]; @@ -67,9 +66,6 @@ static void destroy_video_texture(App *app) { app->texture_width = 0; app->texture_height = 0; } - if (app) { - app->last_guide_preview_update = 0; - } } static void begin_startup_handoff(App *app) { @@ -86,25 +82,8 @@ static int update_video_texture(App *app) { double sync_clock; double lead_tolerance; Uint64 now_ticks; - Uint32 update_ticks; - - if (!app) { - return -1; - } now_ticks = SDL_GetTicks64(); - update_ticks = (Uint32) now_ticks; - - if (app->mode == MODE_GUIDE && - app->video_texture && - update_ticks - app->last_guide_preview_update < GUIDE_PREVIEW_FRAME_INTERVAL_MS) { - if (app->startup_handoff_active && app->startup_handoff_until != 0 && SDL_GetTicks() >= app->startup_handoff_until) { - app->startup_handoff_active = 0; - app->startup_handoff_until = 0; - } - return 0; - } - sync_clock = player_get_sync_clock(&app->player, now_ticks); lead_tolerance = app->startup_handoff_active ? 0.240 : 0.060; @@ -140,10 +119,6 @@ static int update_video_texture(App *app) { frame.pitch_v); frame_data_free(&frame); - if (app->mode == MODE_GUIDE) { - app->last_guide_preview_update = update_ticks; - } - if (app->startup_handoff_active && app->startup_handoff_until != 0 && SDL_GetTicks() >= app->startup_handoff_until) { app->startup_handoff_active = 0; app->startup_handoff_until = 0; @@ -399,11 +374,6 @@ static void handle_event(App *app, const SDL_Event *event) { tune_relative(app, -1); } break; - case SDLK_i: - if (app->mode == MODE_FULLSCREEN) { - app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS; - } - break; case SDLK_LEFT: browse_guide_time(app, -GUIDE_BROWSE_STEP_MINUTES); break; @@ -453,7 +423,7 @@ int app_init(App *app) { return -1; } - app->window = SDL_CreateWindow("FreePassport-C Media Player", + app->window = SDL_CreateWindow("Passport-C Media Player", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, diff --git a/src/app.d b/src/app.d deleted file mode 100644 index 708163b..0000000 --- a/src/app.d +++ /dev/null @@ -1,8 +0,0 @@ -src/app.o: src/app.c src/app.h src/channel.h src/player.h \ - src/frame_queue.h src/theme.h src/ui.h -src/app.h: -src/channel.h: -src/player.h: -src/frame_queue.h: -src/theme.h: -src/ui.h: diff --git a/src/app.h b/src/app.h index 5207292..f8d6912 100644 --- a/src/app.h +++ b/src/app.h @@ -24,7 +24,6 @@ typedef struct App { int startup_handoff_active; int last_blackout_state; Uint32 startup_handoff_until; - Uint32 last_guide_preview_update; Uint32 channel_banner_until; Uint32 channel_switch_lock_until; time_t app_start_time; diff --git a/src/channel.d b/src/channel.d deleted file mode 100644 index 644d8f4..0000000 --- a/src/channel.d +++ /dev/null @@ -1,8 +0,0 @@ -src/channel.o: src/channel.c src/channel.h \ - /usr/include/i386-linux-gnu/sys/stat.h \ - /usr/include/i386-linux-gnu/bits/stat.h \ - /usr/include/i386-linux-gnu/bits/struct_stat.h -src/channel.h: -/usr/include/i386-linux-gnu/sys/stat.h: -/usr/include/i386-linux-gnu/bits/stat.h: -/usr/include/i386-linux-gnu/bits/struct_stat.h: diff --git a/src/frame_queue.c b/src/frame_queue.c index 562e9d4..0a49fd7 100644 --- a/src/frame_queue.c +++ b/src/frame_queue.c @@ -9,114 +9,10 @@ void frame_data_free(FrameData *frame) { return; } - if (frame->pool && frame->buffer) { - video_buffer_pool_release(frame->pool, frame->buffer); - } + av_freep(&frame->buffer); memset(frame, 0, sizeof(*frame)); } -int frame_queue_is_full(FrameQueue *queue) { - int full; - if (!queue || !queue->mutex) { - return 0; - } - SDL_LockMutex(queue->mutex); - full = (queue->count >= FRAME_QUEUE_CAPACITY); - SDL_UnlockMutex(queue->mutex); - return full; -} - -int video_buffer_pool_init(VideoBufferPool *pool) { - int i; - int yuv420p_size; - - if (!pool) { - return -1; - } - - memset(pool, 0, sizeof(*pool)); - pool->mutex = SDL_CreateMutex(); - if (!pool->mutex) { - return -1; - } - - yuv420p_size = TARGET_WIDTH * TARGET_HEIGHT * 3 / 2; - pool->buffer_size = yuv420p_size; - - for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { - pool->buffers[i] = (unsigned char *)av_malloc(yuv420p_size + 32); - if (!pool->buffers[i]) { - video_buffer_pool_destroy(pool); - return -1; - } - pool->available[i] = 1; - } - - return 0; -} - -void video_buffer_pool_destroy(VideoBufferPool *pool) { - int i; - if (!pool) { - return; - } - - if (pool->mutex) { - SDL_LockMutex(pool->mutex); - } - - for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { - if (pool->buffers[i]) { - av_freep(&pool->buffers[i]); - } - } - - if (pool->mutex) { - SDL_UnlockMutex(pool->mutex); - SDL_DestroyMutex(pool->mutex); - } - - memset(pool, 0, sizeof(*pool)); -} - -unsigned char *video_buffer_pool_acquire(VideoBufferPool *pool) { - unsigned char *buffer = NULL; - int i; - - if (!pool || !pool->mutex) { - return NULL; - } - - SDL_LockMutex(pool->mutex); - for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { - if (pool->available[i]) { - pool->available[i] = 0; - buffer = pool->buffers[i]; - break; - } - } - SDL_UnlockMutex(pool->mutex); - - return buffer; -} - -void video_buffer_pool_release(VideoBufferPool *pool, unsigned char *buffer) { - int i; - - if (!pool || !pool->mutex || !buffer) { - return; - } - - SDL_LockMutex(pool->mutex); - for (i = 0; i < VIDEO_BUFFER_POOL_SIZE; i++) { - if (pool->buffers[i] == buffer) { - pool->available[i] = 1; - break; - } - } - SDL_UnlockMutex(pool->mutex); -} - int frame_queue_init(FrameQueue *queue) { if (!queue) { return -1; diff --git a/src/frame_queue.d b/src/frame_queue.d deleted file mode 100644 index 6e86a35..0000000 --- a/src/frame_queue.d +++ /dev/null @@ -1,2 +0,0 @@ -src/frame_queue.o: src/frame_queue.c src/frame_queue.h -src/frame_queue.h: diff --git a/src/frame_queue.h b/src/frame_queue.h index f4ec35a..d4cdf1e 100644 --- a/src/frame_queue.h +++ b/src/frame_queue.h @@ -1,17 +1,12 @@ #ifndef FRAME_QUEUE_H #define FRAME_QUEUE_H -#include #include -#define FRAME_QUEUE_CAPACITY 6 -#define TARGET_WIDTH 640 -#define TARGET_HEIGHT 360 -#define VIDEO_BUFFER_POOL_SIZE 16 +#define FRAME_QUEUE_CAPACITY 12 typedef struct FrameData { unsigned char *buffer; - struct VideoBufferPool *pool; unsigned char *plane_y; unsigned char *plane_u; unsigned char *plane_v; @@ -31,13 +26,6 @@ typedef struct FrameQueue { SDL_cond *cond; } FrameQueue; -typedef struct VideoBufferPool { - unsigned char *buffers[VIDEO_BUFFER_POOL_SIZE]; - int buffer_size; - int available[VIDEO_BUFFER_POOL_SIZE]; - SDL_mutex *mutex; -} VideoBufferPool; - int frame_queue_init(FrameQueue *queue); void frame_queue_destroy(FrameQueue *queue); void frame_queue_clear(FrameQueue *queue); @@ -46,11 +34,5 @@ int frame_queue_pop_latest(FrameQueue *queue, FrameData *out); int frame_queue_peek_first(FrameQueue *queue, FrameData *out); int frame_queue_pop_first(FrameQueue *queue, FrameData *out); void frame_data_free(FrameData *frame); -int frame_queue_is_full(FrameQueue *queue); - -int video_buffer_pool_init(VideoBufferPool *pool); -void video_buffer_pool_destroy(VideoBufferPool *pool); -unsigned char *video_buffer_pool_acquire(VideoBufferPool *pool); -void video_buffer_pool_release(VideoBufferPool *pool, unsigned char *buffer); #endif diff --git a/src/main.d b/src/main.d deleted file mode 100644 index fd0decd..0000000 --- a/src/main.d +++ /dev/null @@ -1,8 +0,0 @@ -src/main.o: src/main.c src/app.h src/channel.h src/player.h \ - src/frame_queue.h src/theme.h src/ui.h -src/app.h: -src/channel.h: -src/player.h: -src/frame_queue.h: -src/theme.h: -src/ui.h: diff --git a/src/player.c b/src/player.c index 32d9c90..e30f03c 100644 --- a/src/player.c +++ b/src/player.c @@ -15,8 +15,6 @@ #include #include -#define AUDIO_BUFFER_SAMPLES 48000 - typedef struct DecoderThreadArgs { Player *player; const Channel *channel; @@ -182,13 +180,13 @@ static int player_ensure_audio_device(Player *player) { } static int queue_audio_frame(Player *player, - SwrContext **swr_context, - AVCodecContext *audio_codec_context, - AVRational audio_time_base, - AVFrame *audio_frame, - double pts_base_offset, - int *queued_any_audio) { - uint8_t *converted_data[1] = {NULL}; + SwrContext **swr_context, + AVCodecContext *audio_codec_context, + AVRational audio_time_base, + AVFrame *audio_frame, + double pts_base_offset, + int *queued_any_audio) { + uint8_t **converted_data = NULL; int out_rate; int out_channels; enum AVSampleFormat out_format; @@ -198,10 +196,10 @@ static int queue_audio_frame(Player *player, int sample_count; int buffer_size; int queued_limit; + int line_size = 0; int result; double audio_pts; double frame_duration; - int wait_ms = 2; if (!player || !audio_codec_context || !audio_frame) { return -1; @@ -238,34 +236,34 @@ static int queue_audio_frame(Player *player, audio_codec_context->sample_rate, AV_ROUND_UP); - if (max_samples > player->audio_convert_buffer_samples) { - int new_size = max_samples * out_channels * sizeof(int16_t); - uint8_t *new_buffer = (uint8_t *)av_realloc(player->audio_convert_buffer, new_size); - if (!new_buffer) { - av_channel_layout_uninit(&out_layout); - player_set_error(player, "Unable to resize audio buffer"); - return -1; - } - player->audio_convert_buffer = new_buffer; - player->audio_convert_buffer_samples = max_samples; - player->audio_convert_buffer_size = new_size; + if (av_samples_alloc_array_and_samples(&converted_data, + &line_size, + out_channels, + max_samples, + out_format, + 0) < 0) { + av_channel_layout_uninit(&out_layout); + player_set_error(player, "Unable to allocate audio buffer"); + return -1; } - converted_data[0] = player->audio_convert_buffer; - sample_count = swr_convert(*swr_context, converted_data, max_samples, (const uint8_t * const *) audio_frame->extended_data, audio_frame->nb_samples); if (sample_count < 0) { + av_freep(&converted_data[0]); + av_freep(&converted_data); av_channel_layout_uninit(&out_layout); player_set_error(player, "Audio resample failed"); return -1; } - buffer_size = sample_count * out_channels * sizeof(int16_t); + buffer_size = av_samples_get_buffer_size(&line_size, out_channels, sample_count, out_format, 1); if (buffer_size <= 0) { + av_freep(&converted_data[0]); + av_freep(&converted_data); av_channel_layout_uninit(&out_layout); return 0; } @@ -277,11 +275,12 @@ static int queue_audio_frame(Player *player, queued_limit = (int) (player_audio_bytes_per_second(player) * 0.18); while (!should_stop(player) && (int) SDL_GetQueuedAudioSize(player->audio_device) > queued_limit) { - SDL_Delay(wait_ms); - wait_ms = (wait_ms < 10) ? wait_ms + 1 : 10; + SDL_Delay(2); } - result = SDL_QueueAudio(player->audio_device, player->audio_convert_buffer, (Uint32) buffer_size); + result = SDL_QueueAudio(player->audio_device, converted_data[0], (Uint32) buffer_size); + av_freep(&converted_data[0]); + av_freep(&converted_data); if (result != 0) { av_channel_layout_uninit(&out_layout); @@ -404,8 +403,8 @@ static int decode_context_open(DecodeContext *ctx, const ProgramEntry *program, ctx->sws_context = sws_getContext(ctx->video_codec_context->width, ctx->video_codec_context->height, ctx->video_codec_context->pix_fmt, - TARGET_WIDTH, - TARGET_HEIGHT, + ctx->video_codec_context->width, + ctx->video_codec_context->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, @@ -613,62 +612,30 @@ static int decode_thread_main(void *userdata) { FrameData frame = {0}; uint8_t *dest_data[4] = {0}; int dest_linesize[4] = {0}; - int needs_conversion = 1; + int image_size; - while (!should_stop(player) && frame_queue_is_full(&player->frame_queue)) { - SDL_Delay(5); - } - if (should_stop(player)) { - break; - } - - frame.width = TARGET_WIDTH; - frame.height = TARGET_HEIGHT; + frame.width = ctx.video_codec_context->width; + frame.height = ctx.video_codec_context->height; frame.pts_seconds = frame_seconds; - - frame.buffer = video_buffer_pool_acquire(&player->video_buffer_pool); - if (!frame.buffer) { - player_set_error(player, "Unable to acquire frame buffer from pool"); + image_size = av_image_alloc(dest_data, dest_linesize, frame.width, frame.height, AV_PIX_FMT_YUV420P, 1); + if (image_size < 0) { + player_set_error(player, "Unable to allocate frame buffer"); goto cleanup; } - - frame.pool = &player->video_buffer_pool; - dest_data[0] = frame.buffer; - dest_data[1] = frame.buffer + (TARGET_WIDTH * TARGET_HEIGHT); - dest_data[2] = frame.buffer + (TARGET_WIDTH * TARGET_HEIGHT * 5 / 4); - dest_linesize[0] = TARGET_WIDTH; - dest_linesize[1] = TARGET_WIDTH / 2; - dest_linesize[2] = TARGET_WIDTH / 2; - - if (ctx.video_codec_context->width == TARGET_WIDTH && - ctx.video_codec_context->height == TARGET_HEIGHT && - ctx.video_codec_context->pix_fmt == AV_PIX_FMT_YUV420P) { - needs_conversion = 0; - } - - if (needs_conversion) { - sws_scale(ctx.sws_context, - (const uint8_t *const *) ctx.decoded_frame->data, - ctx.decoded_frame->linesize, - 0, - ctx.video_codec_context->height, - dest_data, - dest_linesize); - } else { - av_image_copy(dest_data, dest_linesize, - (const uint8_t *const *) ctx.decoded_frame->data, - ctx.decoded_frame->linesize, - AV_PIX_FMT_YUV420P, - TARGET_WIDTH, TARGET_HEIGHT); - } - + frame.buffer = dest_data[0]; frame.plane_y = dest_data[0]; frame.plane_u = dest_data[1]; frame.plane_v = dest_data[2]; frame.pitch_y = dest_linesize[0]; frame.pitch_u = dest_linesize[1]; frame.pitch_v = dest_linesize[2]; - + sws_scale(ctx.sws_context, + (const uint8_t *const *) ctx.decoded_frame->data, + ctx.decoded_frame->linesize, + 0, + ctx.video_codec_context->height, + dest_data, + dest_linesize); frame_queue_push(&player->frame_queue, &frame); queued_first_video = 1; MAYBE_MARK_PREROLL_READY(); @@ -729,28 +696,6 @@ int player_init(Player *player, const ChannelList *channels, Uint64 app_start_ti return -1; } - if (video_buffer_pool_init(&player->video_buffer_pool) != 0) { - frame_queue_destroy(&player->frame_queue); - SDL_DestroyMutex(player->audio_mutex); - SDL_DestroyMutex(player->clock_mutex); - SDL_DestroyMutex(player->error_mutex); - memset(player, 0, sizeof(*player)); - return -1; - } - - player->audio_convert_buffer_samples = AUDIO_BUFFER_SAMPLES; - player->audio_convert_buffer_size = AUDIO_BUFFER_SAMPLES * 2 * sizeof(int16_t); - player->audio_convert_buffer = (uint8_t *)av_malloc(player->audio_convert_buffer_size); - if (!player->audio_convert_buffer) { - video_buffer_pool_destroy(&player->video_buffer_pool); - frame_queue_destroy(&player->frame_queue); - SDL_DestroyMutex(player->audio_mutex); - SDL_DestroyMutex(player->clock_mutex); - SDL_DestroyMutex(player->error_mutex); - memset(player, 0, sizeof(*player)); - return -1; - } - return 0; } @@ -761,10 +706,6 @@ void player_destroy(Player *player) { player_stop_thread(player); player_close_audio(player); - video_buffer_pool_destroy(&player->video_buffer_pool); - if (player->audio_convert_buffer) { - av_freep(&player->audio_convert_buffer); - } frame_queue_destroy(&player->frame_queue); if (player->audio_mutex) { SDL_DestroyMutex(player->audio_mutex); diff --git a/src/player.d b/src/player.d deleted file mode 100644 index 8d072b6..0000000 --- a/src/player.d +++ /dev/null @@ -1,4 +0,0 @@ -src/player.o: src/player.c src/player.h src/channel.h src/frame_queue.h -src/player.h: -src/channel.h: -src/frame_queue.h: diff --git a/src/player.h b/src/player.h index a9fe1be..0568cf6 100644 --- a/src/player.h +++ b/src/player.h @@ -1,7 +1,6 @@ #ifndef PLAYER_H #define PLAYER_H -#include #include #include @@ -10,7 +9,6 @@ typedef struct Player { FrameQueue frame_queue; - VideoBufferPool video_buffer_pool; SDL_Thread *thread; SDL_atomic_t stop_requested; const ChannelList *channels; @@ -29,9 +27,6 @@ typedef struct Player { char audio_driver[32]; SDL_mutex *error_mutex; char last_error[256]; - uint8_t *audio_convert_buffer; - int audio_convert_buffer_size; - int audio_convert_buffer_samples; } Player; int player_init(Player *player, const ChannelList *channels, Uint64 app_start_ticks); diff --git a/src/theme.h b/src/theme.h index 92fbc48..f321b30 100644 --- a/src/theme.h +++ b/src/theme.h @@ -110,9 +110,8 @@ static const SDL_Color COLOR_PILL_SHADOW = {0x00, 0x00, 0x00, 0x40}; #define GUIDE_VISIBLE_ROWS 5 #define TIMELINE_VISIBLE_SECONDS (90.0 * 60.0) #define GUIDE_INFO_WIDTH 390 -#define GUIDE_PREVIEW_WIDTH 240 -#define GUIDE_PREVIEW_HEIGHT 135 -#define GUIDE_TOP_SECTION_GAP 14 +#define GUIDE_PREVIEW_WIDTH 430 +#define GUIDE_PREVIEW_HEIGHT 194 #define GUIDE_STATUS_HEIGHT 42 #define GUIDE_GRID_TOP 212 #define GUIDE_FOOTER_HEIGHT 54 @@ -120,12 +119,6 @@ static const SDL_Color COLOR_PILL_SHADOW = {0x00, 0x00, 0x00, 0x40}; #define GUIDE_X_START 258 #define GUIDE_INFO_HEIGHT 184 -#define CHANNEL_BANNER_HEIGHT 96 -#define CHANNEL_BANNER_PILL_WIDTH 280 -#define CHANNEL_BANNER_PADDING_X 18 -#define CHANNEL_BANNER_PADDING_Y 14 -#define CHANNEL_BANNER_DIVIDER_GAP 14 - #define GUIDE_THEME_COUNT 10 static const GuideTheme GUIDE_THEMES[GUIDE_THEME_COUNT] = { @@ -367,7 +360,7 @@ static const GuideTheme GUIDE_THEMES[GUIDE_THEME_COUNT] = { .block_active_mid = {0x33,0xff,0x00,0xff}, .block_active_bottom = {0x15,0x6e,0x15,0xff}, .block_text = {0x33,0xff,0x00,0xff}, - .block_active_text = {0x00,0x00,0x00,0xff}, + .block_active_text = {0xf5,0xf5,0xf0,0xff}, .block_border = {0x4a,0x8a,0x4a,0xff}, .block_active_border = {0x6a,0xff,0x4a,0xff}, .selection_edge = {0x33,0xff,0x00,0xff}, diff --git a/src/ui.c b/src/ui.c index 2480a54..b7d5416 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,7 +1,6 @@ #include "ui.h" #include -#include #include #include @@ -195,21 +194,13 @@ static void draw_gloss_line(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Co } static void draw_panel_bevel(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color gloss_color) { - SDL_Rect top_highlight = {rect->x + 4, rect->y + 1, SDL_max(rect->w - 8, 0), 2}; - SDL_Rect left_highlight = {rect->x + 1, rect->y + 4, 2, SDL_max(rect->h - 8, 0)}; - SDL_Rect inner_shadow_top = {rect->x + 4, rect->y + 3, SDL_max(rect->w - 8, 0), 1}; - SDL_Rect right_shadow = {rect->x + rect->w - 3, rect->y + 3, 2, SDL_max(rect->h - 6, 0)}; - SDL_Rect bottom_shadow = {rect->x + 3, rect->y + rect->h - 3, SDL_max(rect->w - 6, 0), 2}; + SDL_Rect top_white = {rect->x + 8, rect->y, SDL_max(rect->w - 16, 0), 1}; + SDL_Rect top_black = {rect->x + 8, rect->y + 1, SDL_max(rect->w - 16, 0), 1}; - if (!rect || rect->w <= 6 || rect->h <= 6) { - return; + if (top_white.w > 0) { + fill_rect_alpha(renderer, &top_white, color_with_alpha(gloss_color, 72)); + fill_rect_alpha(renderer, &top_black, (SDL_Color){0, 0, 0, 28}); } - - fill_rect_alpha(renderer, &top_highlight, color_with_alpha(gloss_color, 96)); - fill_rect_alpha(renderer, &left_highlight, color_with_alpha(gloss_color, 64)); - fill_rect_alpha(renderer, &inner_shadow_top, (SDL_Color){0, 0, 0, 34}); - fill_rect_alpha(renderer, &right_shadow, (SDL_Color){0, 0, 0, 56}); - fill_rect_alpha(renderer, &bottom_shadow, (SDL_Color){0, 0, 0, 64}); } static void stroke_rect_alpha(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color color) { @@ -511,14 +502,8 @@ static void draw_mini_status_bar(SDL_Renderer *renderer, const Channel *selected_channel, const SDL_Rect *rect, time_t now_wall) { - char clock_text[32]; - char channel_text[192]; - char fitted_channel_text[192]; + char status_text[256]; SDL_Color text_color; - SDL_Rect time_rect; - SDL_Rect channel_rect; - int time_width = 0; - int gap = 8; if (!rect) { return; @@ -532,33 +517,18 @@ static void draw_mini_status_bar(SDL_Renderer *renderer, theme->status_mid, blend_color(theme->status_bottom, theme->status_mid, 220)); stroke_rect(renderer, rect, theme->panel_border); - draw_panel_bevel(renderer, rect, theme->gloss); - - format_time_compact(clock_text, sizeof(clock_text), now_wall); - if (TTF_SizeUTF8(font, clock_text, &time_width, NULL) != 0) { - time_width = rect->w / 3; - } - - time_rect = (SDL_Rect){rect->x + 8, rect->y, SDL_min(time_width + 4, rect->w - 16), rect->h}; - draw_text_clipped(renderer, font, clock_text, &time_rect, time_rect.x, rect->y + 4, text_color); if (selected_channel) { - channel_rect = (SDL_Rect){time_rect.x + time_rect.w + gap, - rect->y, - rect->x + rect->w - (time_rect.x + time_rect.w + gap) - 8, - rect->h}; - if (channel_rect.w > 0) { - snprintf(channel_text, sizeof(channel_text), "%d %s", selected_channel->number, selected_channel->name); - fit_text_with_ellipsis(font, channel_text, channel_rect.w, fitted_channel_text, sizeof(fitted_channel_text)); - draw_text_clipped(renderer, - font, - fitted_channel_text, - &channel_rect, - channel_rect.x, - rect->y + 4, - text_color); - } + char clock_text[32]; + format_time_compact(clock_text, sizeof(clock_text), now_wall); + snprintf(status_text, sizeof(status_text), "Time %s | Channel %s %d", clock_text, selected_channel->name, selected_channel->number); + } else { + char clock_text[32]; + format_time_compact(clock_text, sizeof(clock_text), now_wall); + snprintf(status_text, sizeof(status_text), "Time %s", clock_text); } + + draw_text_clipped(renderer, font, status_text, rect, rect->x + 10, rect->y + 4, text_color); } static void draw_info_panel(SDL_Renderer *renderer, @@ -587,7 +557,6 @@ static void draw_info_panel(SDL_Renderer *renderer, fill_rect(renderer, rect, theme->panel_fill); stroke_rect(renderer, rect, theme->panel_border); - draw_panel_bevel(renderer, rect, theme->gloss); accent = (SDL_Rect){rect->x + 1, rect->y + 1, rect->w - 2, 36}; fill_rect(renderer, &accent, blend_color(theme->panel_fill, theme->ribbon_mid, 40)); @@ -664,7 +633,6 @@ static void draw_footer_legend(SDL_Renderer *renderer, blend_color(theme->footer_bottom, theme->footer_mid, 240)); draw_gloss_line(renderer, &footer, color_with_alpha(theme->gloss, 20)); stroke_rect(renderer, &footer, theme->panel_border); - draw_panel_bevel(renderer, &footer, theme->gloss); draw_pill_button(renderer, theme, &chip, theme->panel_fill, theme->panel_border); draw_text_clipped(renderer, fonts->small, "A", &footer, chip.x + 11, chip.y + 2, footer_text); @@ -692,6 +660,13 @@ static void draw_footer_legend(SDL_Renderer *renderer, } } +static void draw_scanline_overlay(SDL_Renderer *renderer, int width, int height, const GuideTheme *theme) { + (void) renderer; + (void) width; + (void) height; + (void) theme; +} + static void draw_channel_status_banner(SDL_Renderer *renderer, const UiFonts *fonts, const GuideTheme *theme, @@ -707,9 +682,6 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, SDL_Rect banner; SDL_Rect channel_pill; SDL_Rect info_clip; - int banner_padding_x; - int banner_padding_y; - int divider_x; SDL_Color banner_text; SDL_Color sub_text; char channel_text[96]; @@ -735,18 +707,13 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, return; } - banner_padding_x = CHANNEL_BANNER_PADDING_X; - banner_padding_y = CHANNEL_BANNER_PADDING_Y; - banner = (SDL_Rect){0, window_height - CHANNEL_BANNER_HEIGHT, window_width, CHANNEL_BANNER_HEIGHT}; - channel_pill = (SDL_Rect){banner.x + banner_padding_x, - banner.y + banner_padding_y, - CHANNEL_BANNER_PILL_WIDTH, - banner.h - (banner_padding_y * 2)}; - divider_x = channel_pill.x + channel_pill.w + CHANNEL_BANNER_DIVIDER_GAP; - info_clip = (SDL_Rect){divider_x + CHANNEL_BANNER_DIVIDER_GAP, - banner.y + banner_padding_y, - banner.w - (divider_x + CHANNEL_BANNER_DIVIDER_GAP) - banner_padding_x, - banner.h - (banner_padding_y * 2)}; + banner = (SDL_Rect){window_width / 2 - 360, window_height - 92, 720, 64}; + if (banner.x < 24) { + banner.x = 24; + banner.w = window_width - 48; + } + channel_pill = (SDL_Rect){banner.x + 10, banner.y + 10, 210, banner.h - 20}; + info_clip = (SDL_Rect){channel_pill.x + channel_pill.w + 14, banner.y + 8, banner.w - channel_pill.w - 28, banner.h - 16}; banner_text = ensure_contrast(theme->ribbon_text, theme->status_mid); sub_text = ensure_contrast(theme->row_subtext, theme->status_mid); @@ -767,7 +734,6 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, color_with_alpha(theme->gloss, 42), theme->panel_border); stroke_rect(renderer, &banner, theme->panel_border); - draw_panel_bevel(renderer, &banner, theme->gloss); if (numeric_input_length > 0 || numeric_input_invalid) { SDL_Color accent_fill = theme->ribbon_mid; @@ -803,23 +769,23 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, set_draw_color(renderer, theme->status_divider); SDL_RenderDrawLine(renderer, - divider_x, - banner.y + banner_padding_y, - divider_x, - banner.y + banner.h - banner_padding_y); + channel_pill.x + channel_pill.w + 6, + banner.y + 10, + channel_pill.x + channel_pill.w + 6, + banner.y + banner.h - 10); draw_text_clipped(renderer, fonts->medium, program->program_title, &info_clip, info_clip.x, - banner.y + 18, + banner.y + 10, banner_text); draw_text_clipped(renderer, fonts->small, time_range, &info_clip, info_clip.x, - banner.y + 52, + banner.y + 36, sub_text); } @@ -959,13 +925,11 @@ void ui_render_guide(SDL_Renderer *renderer, int guide_x_start = (int) (GUIDE_X_START * scale_x); int sidebar_width = (int) (GUIDE_SIDEBAR_WIDTH * scale_x); int mini_status_height = (int) (26 * scale_y); - int top_section_gap = (int) (GUIDE_TOP_SECTION_GAP * scale_y); SDL_Rect full = {0, 0, window_width, window_height}; SDL_Rect info_panel = {0, 0, (int) (GUIDE_INFO_WIDTH * scale_x), (int) (GUIDE_INFO_HEIGHT * scale_y)}; SDL_Rect preview = {window_width - (int) (GUIDE_PREVIEW_WIDTH * scale_x), 0, (int) (GUIDE_PREVIEW_WIDTH * scale_x), (int) (GUIDE_PREVIEW_HEIGHT * scale_y)}; int mini_status_y = preview.y + preview.h; - int top_section_bottom = SDL_max(info_panel.y + info_panel.h, mini_status_y + mini_status_height); - int timeline_header_y = top_section_bottom + top_section_gap; + int timeline_header_y = mini_status_y + mini_status_height; int grid_y = timeline_header_y + (int) (GUIDE_STATUS_HEIGHT * scale_y); SDL_Rect mini_status_bar = {preview.x, mini_status_y, preview.w, mini_status_height}; SDL_Rect header_row = {0, timeline_header_y, window_width, (int) (GUIDE_STATUS_HEIGHT * scale_y)}; @@ -981,8 +945,6 @@ void ui_render_guide(SDL_Renderer *renderer, double guide_view_end_seconds = guide_view_start_seconds + TIMELINE_VISIBLE_SECONDS; double guide_focus_seconds = channel_schedule_elapsed_seconds(app_start_time, guide_focus_time); - (void) active_channel; - if (channels && channels->count > 0 && selected_channel >= 0 && selected_channel < channels->count) { selected_channel_ptr = &channels->items[selected_channel]; } @@ -995,8 +957,6 @@ void ui_render_guide(SDL_Renderer *renderer, draw_info_panel(renderer, fonts, theme, selected_channel_ptr, &info_panel, app_start_time, guide_focus_time); draw_panel_shadow(renderer, &preview); fill_rect(renderer, &preview, COLOR_BLACK); - stroke_rect(renderer, &preview, theme->panel_border); - draw_panel_bevel(renderer, &preview, theme->gloss); draw_video(renderer, video_texture, texture_width, texture_height, preview); draw_mini_status_bar(renderer, fonts->small, theme, selected_channel_ptr, &mini_status_bar, now_wall); @@ -1184,6 +1144,7 @@ void ui_render_guide(SDL_Renderer *renderer, } draw_footer_legend(renderer, fonts, theme, window_width, window_height, cache->logo_texture); + draw_scanline_overlay(renderer, window_width, window_height, theme); } void ui_render_theme_picker(SDL_Renderer *renderer, @@ -1237,8 +1198,6 @@ void ui_render_theme_picker(SDL_Renderer *renderer, active_theme->panel_text); } -static char *get_exe_dir(void); - int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, const ChannelList *channels) { char buffer[256]; @@ -1255,7 +1214,7 @@ int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, return -1; } - if (text_texture_init(&cache->no_media_title, renderer, fonts->large, "FreePassport-C Media Player", COLOR_TEXT_LIGHT) != 0 || + if (text_texture_init(&cache->no_media_title, renderer, fonts->large, "Passport-C Media Player", COLOR_TEXT_LIGHT) != 0 || text_texture_init(&cache->no_media_body, renderer, fonts->medium, "No channels found in ./media", COLOR_HIGHLIGHT_YELLOW) != 0 || text_texture_init(&cache->no_media_hint, renderer, fonts->small, "Add MP4 or MKV files to ./media and relaunch.", COLOR_PALE_BLUE) != 0 || text_texture_init(&cache->footer_a, renderer, fonts->medium, "A", COLOR_TEXT_DARK) != 0 || @@ -1268,19 +1227,7 @@ int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, return -1; } - { - char *exe_dir = get_exe_dir(); - if (exe_dir) { - char logo_path[512]; - snprintf(logo_path, sizeof(logo_path), "%slogo.png", exe_dir); - cache->logo_texture = load_png_texture(renderer, logo_path, NULL, NULL); - SDL_free(exe_dir); - } - - if (!cache->logo_texture) { - cache->logo_texture = load_png_texture(renderer, "logo.png", NULL, NULL); - } - } + cache->logo_texture = load_png_texture(renderer, "logo.png", NULL, NULL); if (channels && channels->count > 0) { cache->channels = calloc((size_t) channels->count, sizeof(UiChannelCache)); @@ -1355,36 +1302,12 @@ void ui_cache_destroy(UiCache *cache) { memset(cache, 0, sizeof(*cache)); } -static char *get_exe_dir(void) { - return SDL_GetBasePath(); -} - int ui_load_fonts(UiFonts *fonts) { - char *exe_dir; - if (!fonts) { return -1; } memset(fonts, 0, sizeof(*fonts)); - - exe_dir = get_exe_dir(); - if (exe_dir) { - char font_path[512]; - snprintf(font_path, sizeof(font_path), "%sBigBlueTermPlusNerdFontMono-Regular.ttf", exe_dir); - - fonts->small = TTF_OpenFont(font_path, 13); - fonts->medium = TTF_OpenFont(font_path, 17); - fonts->large = TTF_OpenFont(font_path, 22); - - SDL_free(exe_dir); - - if (fonts->small && fonts->medium && fonts->large) { - return 0; - } - ui_destroy_fonts(fonts); - } - for (size_t i = 0; i < sizeof(FONT_CANDIDATES) / sizeof(FONT_CANDIDATES[0]); ++i) { fonts->small = TTF_OpenFont(FONT_CANDIDATES[i], 13); fonts->medium = TTF_OpenFont(FONT_CANDIDATES[i], 17); diff --git a/src/ui.d b/src/ui.d deleted file mode 100644 index f011b0c..0000000 --- a/src/ui.d +++ /dev/null @@ -1,4 +0,0 @@ -src/ui.o: src/ui.c src/ui.h src/channel.h src/theme.h -src/ui.h: -src/channel.h: -src/theme.h: