Fix guide misalignment
This commit is contained in:
parent
b03fb79f2a
commit
8890d7f527
10 changed files with 167 additions and 33 deletions
|
|
@ -331,7 +331,7 @@ void app_run(App *app) {
|
|||
|
||||
in_blackout = player_is_in_blackout(&app->player);
|
||||
now_ticks = SDL_GetTicks64();
|
||||
now_wall = time(NULL);
|
||||
now_wall = channel_wall_time_from_ticks(app->app_start_time, app->app_start_ticks, now_ticks);
|
||||
if (app->last_blackout_state && !in_blackout) {
|
||||
app->startup_handoff_active = 1;
|
||||
app->startup_handoff_until = SDL_GetTicks() + 400;
|
||||
|
|
|
|||
BIN
src/app.o
BIN
src/app.o
Binary file not shown.
|
|
@ -322,6 +322,17 @@ void channel_list_destroy(ChannelList *list) {
|
|||
list->count = 0;
|
||||
}
|
||||
|
||||
time_t channel_wall_time_from_ticks(time_t app_start_time, Uint64 app_start_ticks, Uint64 now_ticks) {
|
||||
Uint64 elapsed_ticks;
|
||||
|
||||
if (now_ticks < app_start_ticks) {
|
||||
now_ticks = app_start_ticks;
|
||||
}
|
||||
|
||||
elapsed_ticks = now_ticks - app_start_ticks;
|
||||
return app_start_time + (time_t) (elapsed_ticks / 1000);
|
||||
}
|
||||
|
||||
double channel_live_position(const Channel *channel, time_t app_start_time, time_t now) {
|
||||
double elapsed;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ typedef struct ChannelList {
|
|||
|
||||
int channel_list_load(ChannelList *list, const char *media_dir);
|
||||
void channel_list_destroy(ChannelList *list);
|
||||
time_t channel_wall_time_from_ticks(time_t app_start_time, Uint64 app_start_ticks, Uint64 now_ticks);
|
||||
double channel_live_position(const Channel *channel, time_t app_start_time, time_t now);
|
||||
double channel_live_position_precise(const Channel *channel, Uint64 app_start_ticks, Uint64 now_ticks);
|
||||
const ProgramEntry *channel_resolve_program(const Channel *channel,
|
||||
|
|
|
|||
BIN
src/channel.o
BIN
src/channel.o
Binary file not shown.
Binary file not shown.
121
src/player.c
121
src/player.c
|
|
@ -419,6 +419,80 @@ static int decode_context_open(DecodeContext *ctx, const ProgramEntry *program,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int decode_context_seek_to_seconds(DecodeContext *ctx, double seek_seconds) {
|
||||
int64_t seek_target;
|
||||
|
||||
if (!ctx || seek_seconds <= 0.0 || !ctx->format_context || ctx->video_stream_index < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
seek_target = (int64_t) (seek_seconds / av_q2d(ctx->video_time_base));
|
||||
if (avformat_seek_file(ctx->format_context, ctx->video_stream_index, INT64_MIN, seek_target, INT64_MAX, 0) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
avcodec_flush_buffers(ctx->video_codec_context);
|
||||
if (ctx->audio_codec_context) {
|
||||
avcodec_flush_buffers(ctx->audio_codec_context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_context_sync_to_schedule(Player *player,
|
||||
const Channel *channel,
|
||||
DecodeContext *ctx,
|
||||
const ProgramEntry **program,
|
||||
int *current_program_index,
|
||||
double *seek_seconds,
|
||||
int force_reopen,
|
||||
int *queued_any_audio,
|
||||
int *queued_first_video) {
|
||||
const ProgramEntry *scheduled_program;
|
||||
int scheduled_program_index = -1;
|
||||
double scheduled_seek_seconds = 0.0;
|
||||
|
||||
if (!player || !channel || !ctx || !program || !current_program_index || !seek_seconds) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
scheduled_program = channel_resolve_program(channel,
|
||||
player->app_start_ticks,
|
||||
SDL_GetTicks64(),
|
||||
&scheduled_seek_seconds,
|
||||
&scheduled_program_index);
|
||||
if (!scheduled_program) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!force_reopen && *program == scheduled_program && *current_program_index == scheduled_program_index) {
|
||||
*seek_seconds = scheduled_seek_seconds;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (decode_context_open(ctx, scheduled_program, player) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (decode_context_seek_to_seconds(ctx, scheduled_seek_seconds) != 0) {
|
||||
player_set_error(player, "Unable to seek to scheduled program position");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_queue_clear(&player->frame_queue);
|
||||
player_clear_audio(player);
|
||||
*program = scheduled_program;
|
||||
*current_program_index = scheduled_program_index;
|
||||
*seek_seconds = scheduled_seek_seconds;
|
||||
if (queued_any_audio) {
|
||||
*queued_any_audio = 0;
|
||||
}
|
||||
if (queued_first_video) {
|
||||
*queued_first_video = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_thread_main(void *userdata) {
|
||||
DecoderThreadArgs *args = (DecoderThreadArgs *) userdata;
|
||||
Player *player = args->player;
|
||||
|
|
@ -438,29 +512,42 @@ static int decode_thread_main(void *userdata) {
|
|||
ctx.audio_stream_index = -1;
|
||||
free(args);
|
||||
|
||||
program = channel_resolve_program(channel, player->app_start_ticks, SDL_GetTicks64(), &seek_seconds, ¤t_program_index);
|
||||
if (!program || decode_context_open(&ctx, program, player) != 0) {
|
||||
if (decode_context_sync_to_schedule(player,
|
||||
channel,
|
||||
&ctx,
|
||||
&program,
|
||||
¤t_program_index,
|
||||
&seek_seconds,
|
||||
1,
|
||||
&queued_any_audio,
|
||||
&queued_first_video) != 0) {
|
||||
decode_context_reset(&ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (seek_seconds > 0.0) {
|
||||
int64_t seek_target = (int64_t) (seek_seconds / av_q2d(ctx.video_time_base));
|
||||
avformat_seek_file(ctx.format_context, ctx.video_stream_index, INT64_MIN, seek_target, INT64_MAX, 0);
|
||||
avcodec_flush_buffers(ctx.video_codec_context);
|
||||
if (ctx.audio_codec_context) {
|
||||
avcodec_flush_buffers(ctx.audio_codec_context);
|
||||
}
|
||||
}
|
||||
player_clear_audio(player);
|
||||
|
||||
while (!should_stop(player)) {
|
||||
if (decode_context_sync_to_schedule(player,
|
||||
channel,
|
||||
&ctx,
|
||||
&program,
|
||||
¤t_program_index,
|
||||
&seek_seconds,
|
||||
0,
|
||||
&queued_any_audio,
|
||||
&queued_first_video) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (av_read_frame(ctx.format_context, ctx.packet) < 0) {
|
||||
current_program_index = (current_program_index + 1) % channel->program_count;
|
||||
program = channel_program_at_index(channel, current_program_index);
|
||||
queued_any_audio = 0;
|
||||
queued_first_video = 0;
|
||||
if (!program || decode_context_open(&ctx, program, player) != 0) {
|
||||
if (decode_context_sync_to_schedule(player,
|
||||
channel,
|
||||
&ctx,
|
||||
&program,
|
||||
¤t_program_index,
|
||||
&seek_seconds,
|
||||
1,
|
||||
&queued_any_audio,
|
||||
&queued_first_video) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
continue;
|
||||
|
|
|
|||
BIN
src/player.o
BIN
src/player.o
Binary file not shown.
65
src/ui.c
65
src/ui.c
|
|
@ -378,8 +378,18 @@ static void format_clock_label(char *buffer, size_t buffer_size, time_t now, int
|
|||
strftime(buffer, buffer_size, "%I:%M %p", &local_time);
|
||||
}
|
||||
|
||||
static void draw_timeline_header_cached(SDL_Renderer *renderer, const UiCache *cache, const GuideTheme *theme, SDL_Rect rect) {
|
||||
int segments = 4;
|
||||
static int timeline_marker_x(int guide_x_start, double pixels_per_minute, int minute_offset) {
|
||||
return guide_x_start + (int) ((minute_offset * pixels_per_minute) + 0.5);
|
||||
}
|
||||
|
||||
static void draw_timeline_header_cached(SDL_Renderer *renderer,
|
||||
const UiCache *cache,
|
||||
const GuideTheme *theme,
|
||||
SDL_Rect rect,
|
||||
int guide_x_start,
|
||||
double pixels_per_minute,
|
||||
int timeline_width,
|
||||
int visible_minutes) {
|
||||
SDL_Color ribbon_text = ensure_contrast(theme->ribbon_text, theme->ribbon_mid);
|
||||
SDL_Color ribbon_shadow = color_luma(theme->ribbon_mid) < 120 ? color_with_alpha(COLOR_BLACK, 255) : color_with_alpha(COLOR_TEXT_LIGHT, 255);
|
||||
|
||||
|
|
@ -391,11 +401,25 @@ static void draw_timeline_header_cached(SDL_Renderer *renderer, const UiCache *c
|
|||
theme->gloss,
|
||||
theme->panel_border);
|
||||
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
int x = rect.x + (rect.w * i) / segments;
|
||||
(void) timeline_width;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int minute_offset = (visible_minutes / 3) * i;
|
||||
int x = timeline_marker_x(guide_x_start, pixels_per_minute, minute_offset);
|
||||
int centered_x = x - cache->timeline_labels[i].width / 2;
|
||||
int min_center_x = guide_x_start;
|
||||
int max_center_x = guide_x_start + timeline_width - cache->timeline_labels[i].width;
|
||||
SDL_Rect shadow_dst = {centered_x + 1, rect.y + 11, cache->timeline_labels[i].width, cache->timeline_labels[i].height};
|
||||
SDL_Rect text_dst = {centered_x, rect.y + 10, cache->timeline_labels[i].width, cache->timeline_labels[i].height};
|
||||
|
||||
if (centered_x < min_center_x) {
|
||||
centered_x = min_center_x;
|
||||
}
|
||||
if (centered_x > max_center_x) {
|
||||
centered_x = max_center_x;
|
||||
}
|
||||
shadow_dst.x = centered_x + 1;
|
||||
text_dst.x = centered_x;
|
||||
|
||||
SDL_SetTextureColorMod(cache->timeline_labels[i].texture, ribbon_shadow.r, ribbon_shadow.g, ribbon_shadow.b);
|
||||
SDL_RenderCopy(renderer, cache->timeline_labels[i].texture, NULL, &shadow_dst);
|
||||
SDL_SetTextureColorMod(cache->timeline_labels[i].texture, ribbon_text.r, ribbon_text.g, ribbon_text.b);
|
||||
|
|
@ -493,7 +517,12 @@ static void draw_info_panel(SDL_Renderer *renderer,
|
|||
draw_text_clipped(renderer, fonts->small, description, &clip_rect, rect->x + 18, rect->y + 148, panel_text);
|
||||
}
|
||||
|
||||
static void draw_grid_background(SDL_Renderer *renderer, const GuideTheme *theme, const SDL_Rect *grid_rect, int row_height, double pixels_per_minute) {
|
||||
static void draw_grid_background(SDL_Renderer *renderer,
|
||||
const GuideTheme *theme,
|
||||
const SDL_Rect *grid_rect,
|
||||
int guide_x_start,
|
||||
int row_height,
|
||||
double pixels_per_minute) {
|
||||
fill_three_stop_gradient(renderer,
|
||||
grid_rect,
|
||||
theme->background_top,
|
||||
|
|
@ -501,7 +530,7 @@ static void draw_grid_background(SDL_Renderer *renderer, const GuideTheme *theme
|
|||
theme->background_bottom);
|
||||
|
||||
for (int minute = 0; minute <= 90; minute += 30) {
|
||||
int x = GUIDE_X_START + (int) (minute * pixels_per_minute);
|
||||
int x = timeline_marker_x(guide_x_start, pixels_per_minute, minute);
|
||||
SDL_Color strong_line = ensure_contrast(theme->grid_line, theme->background_mid);
|
||||
set_draw_color(renderer, strong_line);
|
||||
SDL_RenderDrawLine(renderer, x, grid_rect->y, x, grid_rect->y + grid_rect->h);
|
||||
|
|
@ -674,6 +703,7 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
int start_index = active_channel - 2;
|
||||
const Channel *selected_channel = NULL;
|
||||
double pixels_per_minute = timeline_w / 90.0;
|
||||
time_t guide_view_start_time = now_wall - (30 * 60);
|
||||
|
||||
if (channels && channels->count > 0 && active_channel >= 0 && active_channel < channels->count) {
|
||||
selected_channel = &channels->items[active_channel];
|
||||
|
|
@ -693,15 +723,22 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
if (cache->timeline_label_slot != now_wall / 60 || cache->timeline_theme != theme) {
|
||||
char label[32];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
format_clock_label(label, sizeof(label), now_wall, (int) ((TIMELINE_VISIBLE_SECONDS / 60.0 / 4) * i));
|
||||
format_clock_label(label, sizeof(label), guide_view_start_time, 30 * i);
|
||||
text_texture_destroy(&cache->timeline_labels[i]);
|
||||
text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_LIGHT);
|
||||
}
|
||||
cache->timeline_label_slot = now_wall / 60;
|
||||
cache->timeline_theme = theme;
|
||||
}
|
||||
draw_timeline_header_cached(renderer, cache, theme, header_row);
|
||||
draw_grid_background(renderer, theme, &grid, row_height, pixels_per_minute);
|
||||
draw_timeline_header_cached(renderer,
|
||||
cache,
|
||||
theme,
|
||||
header_row,
|
||||
guide_x_start,
|
||||
pixels_per_minute,
|
||||
timeline_w,
|
||||
(int) (TIMELINE_VISIBLE_SECONDS / 60.0));
|
||||
draw_grid_background(renderer, theme, &grid, guide_x_start, row_height, pixels_per_minute);
|
||||
|
||||
if (start_index < 0) {
|
||||
start_index = 0;
|
||||
|
|
@ -741,11 +778,10 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
|
||||
{
|
||||
const Channel *channel = &channels->items[channel_index];
|
||||
double live_position = 0.0;
|
||||
double program_seek = 0.0;
|
||||
const ProgramEntry *program = channel_resolve_program(channel, app_start_ticks, now_ticks, &program_seek, NULL);
|
||||
time_t guide_view_start_time = now_wall - (30 * 60);
|
||||
time_t program_start_time = now_wall - (time_t) live_position;
|
||||
double guide_view_start_seconds = (double) guide_view_start_time;
|
||||
double program_start_time = (double) now_wall;
|
||||
int block_x = guide_x_start;
|
||||
int block_w = 48;
|
||||
SDL_Rect block = {guide_x_start, timeline_rect.y + 4, 48, timeline_rect.h - 8};
|
||||
|
|
@ -764,9 +800,8 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
if (!program) {
|
||||
continue;
|
||||
}
|
||||
live_position = channel_live_position_precise(channel, app_start_ticks, now_ticks);
|
||||
program_start_time = now_wall - (time_t) program_seek;
|
||||
block_x = guide_x_start + (int) (((double) (program_start_time - guide_view_start_time)) / 60.0 * pixels_per_minute);
|
||||
program_start_time -= program_seek;
|
||||
block_x = guide_x_start + (int) ((((program_start_time - guide_view_start_seconds) / 60.0) * pixels_per_minute) + 0.5);
|
||||
block_w = (int) ((program->duration_seconds / 60.0) * pixels_per_minute);
|
||||
block = (SDL_Rect){block_x, timeline_rect.y + 4, SDL_max(block_w, 48), timeline_rect.h - 8};
|
||||
title_rect = (SDL_Rect){block.x + 8, block.y + 8, block.w - 16, block.h - 16};
|
||||
|
|
|
|||
BIN
src/ui.o
BIN
src/ui.o
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue