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);
|
in_blackout = player_is_in_blackout(&app->player);
|
||||||
now_ticks = SDL_GetTicks64();
|
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) {
|
if (app->last_blackout_state && !in_blackout) {
|
||||||
app->startup_handoff_active = 1;
|
app->startup_handoff_active = 1;
|
||||||
app->startup_handoff_until = SDL_GetTicks() + 400;
|
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;
|
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 channel_live_position(const Channel *channel, time_t app_start_time, time_t now) {
|
||||||
double elapsed;
|
double elapsed;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ typedef struct ChannelList {
|
||||||
|
|
||||||
int channel_list_load(ChannelList *list, const char *media_dir);
|
int channel_list_load(ChannelList *list, const char *media_dir);
|
||||||
void channel_list_destroy(ChannelList *list);
|
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(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);
|
double channel_live_position_precise(const Channel *channel, Uint64 app_start_ticks, Uint64 now_ticks);
|
||||||
const ProgramEntry *channel_resolve_program(const Channel *channel,
|
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;
|
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) {
|
static int decode_thread_main(void *userdata) {
|
||||||
DecoderThreadArgs *args = (DecoderThreadArgs *) userdata;
|
DecoderThreadArgs *args = (DecoderThreadArgs *) userdata;
|
||||||
Player *player = args->player;
|
Player *player = args->player;
|
||||||
|
|
@ -438,29 +512,42 @@ static int decode_thread_main(void *userdata) {
|
||||||
ctx.audio_stream_index = -1;
|
ctx.audio_stream_index = -1;
|
||||||
free(args);
|
free(args);
|
||||||
|
|
||||||
program = channel_resolve_program(channel, player->app_start_ticks, SDL_GetTicks64(), &seek_seconds, ¤t_program_index);
|
if (decode_context_sync_to_schedule(player,
|
||||||
if (!program || decode_context_open(&ctx, program, player) != 0) {
|
channel,
|
||||||
|
&ctx,
|
||||||
|
&program,
|
||||||
|
¤t_program_index,
|
||||||
|
&seek_seconds,
|
||||||
|
1,
|
||||||
|
&queued_any_audio,
|
||||||
|
&queued_first_video) != 0) {
|
||||||
decode_context_reset(&ctx);
|
decode_context_reset(&ctx);
|
||||||
return -1;
|
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)) {
|
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) {
|
if (av_read_frame(ctx.format_context, ctx.packet) < 0) {
|
||||||
current_program_index = (current_program_index + 1) % channel->program_count;
|
if (decode_context_sync_to_schedule(player,
|
||||||
program = channel_program_at_index(channel, current_program_index);
|
channel,
|
||||||
queued_any_audio = 0;
|
&ctx,
|
||||||
queued_first_video = 0;
|
&program,
|
||||||
if (!program || decode_context_open(&ctx, program, player) != 0) {
|
¤t_program_index,
|
||||||
|
&seek_seconds,
|
||||||
|
1,
|
||||||
|
&queued_any_audio,
|
||||||
|
&queued_first_video) != 0) {
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
continue;
|
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);
|
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) {
|
static int timeline_marker_x(int guide_x_start, double pixels_per_minute, int minute_offset) {
|
||||||
int segments = 4;
|
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_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);
|
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->gloss,
|
||||||
theme->panel_border);
|
theme->panel_border);
|
||||||
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
(void) timeline_width;
|
||||||
int x = rect.x + (rect.w * i) / segments;
|
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 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 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};
|
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_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_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);
|
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);
|
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,
|
fill_three_stop_gradient(renderer,
|
||||||
grid_rect,
|
grid_rect,
|
||||||
theme->background_top,
|
theme->background_top,
|
||||||
|
|
@ -501,7 +530,7 @@ static void draw_grid_background(SDL_Renderer *renderer, const GuideTheme *theme
|
||||||
theme->background_bottom);
|
theme->background_bottom);
|
||||||
|
|
||||||
for (int minute = 0; minute <= 90; minute += 30) {
|
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);
|
SDL_Color strong_line = ensure_contrast(theme->grid_line, theme->background_mid);
|
||||||
set_draw_color(renderer, strong_line);
|
set_draw_color(renderer, strong_line);
|
||||||
SDL_RenderDrawLine(renderer, x, grid_rect->y, x, grid_rect->y + grid_rect->h);
|
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;
|
int start_index = active_channel - 2;
|
||||||
const Channel *selected_channel = NULL;
|
const Channel *selected_channel = NULL;
|
||||||
double pixels_per_minute = timeline_w / 90.0;
|
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) {
|
if (channels && channels->count > 0 && active_channel >= 0 && active_channel < channels->count) {
|
||||||
selected_channel = &channels->items[active_channel];
|
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) {
|
if (cache->timeline_label_slot != now_wall / 60 || cache->timeline_theme != theme) {
|
||||||
char label[32];
|
char label[32];
|
||||||
for (int i = 0; i < 4; ++i) {
|
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_destroy(&cache->timeline_labels[i]);
|
||||||
text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_LIGHT);
|
text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_LIGHT);
|
||||||
}
|
}
|
||||||
cache->timeline_label_slot = now_wall / 60;
|
cache->timeline_label_slot = now_wall / 60;
|
||||||
cache->timeline_theme = theme;
|
cache->timeline_theme = theme;
|
||||||
}
|
}
|
||||||
draw_timeline_header_cached(renderer, cache, theme, header_row);
|
draw_timeline_header_cached(renderer,
|
||||||
draw_grid_background(renderer, theme, &grid, row_height, pixels_per_minute);
|
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) {
|
if (start_index < 0) {
|
||||||
start_index = 0;
|
start_index = 0;
|
||||||
|
|
@ -741,11 +778,10 @@ void ui_render_guide(SDL_Renderer *renderer,
|
||||||
|
|
||||||
{
|
{
|
||||||
const Channel *channel = &channels->items[channel_index];
|
const Channel *channel = &channels->items[channel_index];
|
||||||
double live_position = 0.0;
|
|
||||||
double program_seek = 0.0;
|
double program_seek = 0.0;
|
||||||
const ProgramEntry *program = channel_resolve_program(channel, app_start_ticks, now_ticks, &program_seek, NULL);
|
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);
|
double guide_view_start_seconds = (double) guide_view_start_time;
|
||||||
time_t program_start_time = now_wall - (time_t) live_position;
|
double program_start_time = (double) now_wall;
|
||||||
int block_x = guide_x_start;
|
int block_x = guide_x_start;
|
||||||
int block_w = 48;
|
int block_w = 48;
|
||||||
SDL_Rect block = {guide_x_start, timeline_rect.y + 4, 48, timeline_rect.h - 8};
|
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) {
|
if (!program) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
live_position = channel_live_position_precise(channel, app_start_ticks, now_ticks);
|
program_start_time -= program_seek;
|
||||||
program_start_time = now_wall - (time_t) program_seek;
|
block_x = guide_x_start + (int) ((((program_start_time - guide_view_start_seconds) / 60.0) * pixels_per_minute) + 0.5);
|
||||||
block_x = guide_x_start + (int) (((double) (program_start_time - guide_view_start_time)) / 60.0 * pixels_per_minute);
|
|
||||||
block_w = (int) ((program->duration_seconds / 60.0) * pixels_per_minute);
|
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};
|
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};
|
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