97 if (!config || !config->run_fn) {
98 return SET_ERRNO(ERROR_INVALID_PARAM,
"config or run_fn is NULL");
102 g_current_config = config;
105 g_render_should_exit = display_should_exit_adapter;
107 asciichat_error_t result = ASCIICHAT_OK;
111 session_capture_ctx_t *capture = NULL;
113 audio_context_t *audio_ctx = NULL;
115 bool audio_available =
false;
121 log_debug(
"session_client_like_run(): Setting up terminal and logging");
125 log_debug(
"terminal_should_force_stderr()=%d", should_force_stderr);
127 if (should_force_stderr) {
137 log_debug(
"session_client_like_run(): Validating keepawake options");
140 bool enable_ka = GET_OPTION(enable_keepawake);
141 bool disable_ka = GET_OPTION(disable_keepawake);
142 log_debug(
"enable_keepawake=%d, disable_keepawake=%d", enable_ka, disable_ka);
144 if (enable_ka && disable_ka) {
145 result = SET_ERRNO(ERROR_INVALID_PARAM,
"--keepawake and --no-keepawake are mutually exclusive");
149 log_debug(
"session_client_like_run(): Enabling keepawake if needed");
156 log_debug(
"session_client_like_run(): Keepawake setup complete");
162 log_debug(
"session_client_like_run(): Creating temporary display for splash");
169 const char *media_url = GET_OPTION(media_url);
170 const char *media_file = GET_OPTION(media_file);
171 bool has_media = (media_url && strlen(media_url) > 0) || (media_file && strlen(media_file) > 0);
174 if (!has_media && !GET_OPTION(snapshot_mode)) {
183 if (!GET_OPTION(snapshot_mode)) {
191 session_capture_config_t capture_config = {0};
192 capture_config.resize_for_network =
false;
193 capture_config.should_exit_callback = capture_should_exit_adapter;
194 capture_config.callback_data = NULL;
197 int user_fps = GET_OPTION(fps);
198 bool fps_explicitly_set = user_fps > 0;
201 const char *media_url_val = GET_OPTION(media_url);
202 const char *media_file_val = GET_OPTION(media_file);
204 if (media_url_val && strlen(media_url_val) > 0) {
206 log_info(
"Using network URL: %s (webcam disabled)", media_url_val);
207 capture_config.type = MEDIA_SOURCE_FILE;
208 capture_config.path = media_url_val;
209 capture_config.loop =
false;
211 if (fps_explicitly_set) {
212 capture_config.target_fps = (uint32_t)user_fps;
213 log_info(
"Using user-specified FPS: %u", capture_config.target_fps);
219 log_info(
"Detected HTTP stream video FPS: %.1f", url_fps);
221 capture_config.target_fps = (uint32_t)(url_fps + 0.5);
223 log_warn(
"FPS detection failed for HTTP stream, using default 60 FPS");
224 capture_config.target_fps = 60;
227 log_warn(
"Failed to create probe source for HTTP stream, using default 60 FPS");
228 capture_config.target_fps = 60;
231 }
else if (media_file_val && strlen(media_file_val) > 0) {
233 if (strcmp(media_file_val,
"-") == 0) {
234 log_info(
"Using stdin for media streaming (webcam disabled)");
235 capture_config.type = MEDIA_SOURCE_STDIN;
236 capture_config.path = NULL;
237 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
238 capture_config.loop =
false;
240 log_info(
"Using media file: %s (webcam disabled)", media_file_val);
241 capture_config.type = MEDIA_SOURCE_FILE;
242 capture_config.path = media_file_val;
243 capture_config.loop = GET_OPTION(media_loop);
245 if (fps_explicitly_set) {
246 capture_config.target_fps = (uint32_t)user_fps;
247 log_info(
"Using user-specified FPS: %u", capture_config.target_fps);
253 log_info(
"Detected file video FPS: %.1f", file_fps);
254 if (file_fps > 0.0) {
255 capture_config.target_fps = (uint32_t)(file_fps + 0.5);
257 log_warn(
"FPS detection failed, using default 60 FPS");
258 capture_config.target_fps = 60;
261 log_warn(
"Failed to create probe source for FPS detection, using default 60 FPS");
262 capture_config.target_fps = 60;
266 }
else if (GET_OPTION(test_pattern)) {
268 log_info(
"Using test pattern");
269 capture_config.type = MEDIA_SOURCE_TEST;
270 capture_config.path = NULL;
271 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
272 capture_config.loop =
false;
275 log_info(
"Using local webcam");
276 capture_config.type = MEDIA_SOURCE_WEBCAM;
277 capture_config.path = NULL;
278 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
279 capture_config.loop =
false;
283 capture_config.initial_seek_timestamp = GET_OPTION(media_seek_timestamp);
298 bool is_network_mode = (config->tcp_client != NULL || config->websocket_client != NULL);
300 if (is_network_mode) {
302 log_debug(
"Network mode detected - using network capture (no local media source)");
303 int fps = GET_OPTION(fps);
306 log_fatal(
"Failed to initialize network capture context");
307 result = ERROR_MEDIA_INIT;
311 log_debug(
"Network capture FPS set to %d from options", fps);
315 log_debug(
"Mirror mode detected - using mirror capture with local media source");
318 log_fatal(
"Failed to initialize mirror capture source");
319 result = ERROR_MEDIA_INIT;
329 bool should_init_audio =
true;
330 if (GET_OPTION(snapshot_mode) && GET_OPTION(snapshot_delay) == 0.0) {
331 should_init_audio =
false;
332 log_debug(
"Skipping audio initialization for immediate snapshot");
336 if (should_init_audio && capture_config.type == MEDIA_SOURCE_FILE && capture_config.path) {
339 audio_available =
true;
342 audio_ctx = SAFE_MALLOC(
sizeof(audio_context_t), audio_context_t *);
344 *audio_ctx = (audio_context_t){0};
348 audio_ctx->media_source = media_source;
356 audio_ctx->playback_only = !should_enable_mic;
359 if (audio_ctx->playback_buffer) {
360 audio_ctx->playback_buffer->jitter_buffer_enabled =
false;
361 atomic_store(&audio_ctx->playback_buffer->jitter_buffer_filled,
true);
367 log_debug(
"Audio context initialized");
369 log_warn(
"Failed to initialize audio context");
371 SAFE_FREE(audio_ctx);
373 audio_available =
false;
383 session_display_config_t display_config = {0};
384 display_config.snapshot_mode = GET_OPTION(snapshot_mode);
385 display_config.palette_type = GET_OPTION(palette_type);
386 display_config.custom_palette = GET_OPTION(palette_custom_set) ? GET_OPTION(palette_custom) : NULL;
387 display_config.color_mode = TERM_COLOR_AUTO;
388 display_config.enable_audio_playback = audio_available;
389 display_config.audio_ctx = audio_ctx;
390 display_config.should_exit_callback = display_should_exit_adapter;
391 display_config.callback_data = NULL;
395 log_fatal(
"Failed to initialize display");
396 result = ERROR_DISPLAY;
404 log_debug(
"About to call splash_intro_done()");
406 log_debug(
"splash_intro_done() returned");
417 log_debug(
"About to check audio context for duplex");
418 if (audio_ctx && audio_available) {
420 log_info(
"Audio playback started");
422 log_warn(
"Failed to start audio duplex");
424 SAFE_FREE(audio_ctx);
426 audio_available =
false;
434 log_debug(
"session_client_like_run(): Setting up network transports");
437 const char *server_address = GET_OPTION(address);
441 log_debug(
"WebSocket URL detected: %s", server_address);
443 if (!g_websocket_client) {
444 log_error(
"Failed to create WebSocket client");
445 result = ERROR_NETWORK;
449 log_debug(
"Using TCP client for server: %s:%d", server_address ? server_address :
"localhost", GET_OPTION(port));
452 log_error(
"Failed to create TCP client");
453 result = ERROR_NETWORK;
464 int max_attempts = config->max_reconnect_attempts;
469 log_debug(
"About to call config->run_fn() (attempt %d)", attempt);
470 result = config->run_fn(capture, display, config->run_user_data);
471 log_debug(
"config->run_fn() returned with result=%d", result);
474 if (result == ASCIICHAT_OK) {
479 bool should_retry =
false;
481 if (max_attempts != 0) {
483 if (config->should_reconnect_callback) {
484 should_retry = config->should_reconnect_callback(result, attempt, config->reconnect_user_data);
490 if (should_retry && max_attempts > 0 && attempt >= max_attempts) {
491 log_debug(
"Reached maximum reconnection attempts (%d), giving up", max_attempts);
492 should_retry =
false;
502 if (max_attempts == -1) {
503 log_info(
"Connection failed, retrying...");
504 }
else if (max_attempts > 0) {
505 log_info(
"Connection failed, retrying (attempt %d/%d)...", attempt + 1, max_attempts);
509 if (config->reconnect_delay_ms > 0) {
525 if (g_websocket_client) {
526 log_debug(
"Destroying WebSocket client");
530 log_debug(
"Destroying TCP client");
535 log_debug(
"Terminating PortAudio device resources");
542 SAFE_FREE(audio_ctx);
571 log_debug(
"Disabling keepawake");
576 const char newline =
'\n';