ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
participant.c File Reference

👤 Client-side session participation implementation More...

Go to the source code of this file.

Data Structures

struct  session_participant
 Internal session participant structure. More...
 

Typedefs

typedef struct session_participant session_participant_t
 Internal session participant structure.
 

Functions

session_participant_tsession_participant_create (const session_participant_config_t *config)
 
void session_participant_destroy (session_participant_t *p)
 
asciichat_error_t session_participant_connect (session_participant_t *p)
 
void session_participant_disconnect (session_participant_t *p)
 
bool session_participant_is_connected (session_participant_t *p)
 
uint32_t session_participant_get_client_id (session_participant_t *p)
 
socket_t session_participant_get_socket (session_participant_t *p)
 
asciichat_error_t session_participant_start_video (session_participant_t *p)
 
void session_participant_stop_video (session_participant_t *p)
 
bool session_participant_is_video_active (session_participant_t *p)
 
asciichat_error_t session_participant_start_audio (session_participant_t *p)
 
void session_participant_stop_audio (session_participant_t *p)
 
bool session_participant_is_audio_active (session_participant_t *p)
 
asciichat_error_t session_participant_get_settings (session_participant_t *p, session_settings_t *settings)
 
asciichat_error_t session_participant_request_settings (session_participant_t *p, const session_settings_t *settings)
 
asciichat_error_t session_participant_start_video_capture (session_participant_t *p)
 
void session_participant_stop_video_capture (session_participant_t *p)
 
asciichat_error_t session_participant_start_audio_capture (session_participant_t *p)
 
void session_participant_stop_audio_capture (session_participant_t *p)
 
asciichat_error_t session_participant_set_transport (session_participant_t *p, acip_transport_t *transport)
 
acip_transport_t * session_participant_get_transport (session_participant_t *p)
 
bool session_participant_has_transport (session_participant_t *p)
 

Detailed Description

👤 Client-side session participation implementation

Implements the session participant abstraction for client-side connection management and media streaming.

NOTE: This is a stub implementation that provides the API structure. Full implementation will integrate with existing client code in a future phase.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file participant.c.

Typedef Documentation

◆ session_participant_t

Internal session participant structure.

Contains connection state, media stream state, and callback configuration.

Function Documentation

◆ session_participant_connect()

asciichat_error_t session_participant_connect ( session_participant_t p)

Definition at line 272 of file participant.c.

272 {
273 log_debug("session_participant_connect: enter - p=%p", p);
274
275 if (!p || !p->initialized) {
276 log_debug("session_participant_connect: invalid param or not initialized");
277 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_connect: invalid participant");
278 }
279
280 log_debug("session_participant_connect: p->initialized=%d, p->connected=%d", p->initialized, p->connected);
281
282 if (p->connected) {
283 log_debug("session_participant_connect: already connected, returning OK");
284 return ASCIICHAT_OK; // Already connected
285 }
286
287 // Create TCP connection to server
288 log_debug("session_participant_connect: calling connect_to_server(%s, %d)", p->address, p->port);
289 p->socket = connect_to_server(p->address, p->port);
290 log_debug("session_participant_connect: connect_to_server returned socket=%d (INVALID=%d)", p->socket,
291 INVALID_SOCKET_VALUE);
292
293 if (p->socket == INVALID_SOCKET_VALUE) {
294 log_error("Failed to connect to server at %s:%d", p->address, p->port);
295 if (p->callbacks.on_error) {
296 p->callbacks.on_error(p, ERROR_NETWORK_CONNECT, "Failed to connect to server", p->user_data);
297 }
298 return GET_ERRNO();
299 }
300
301 log_debug("session_participant_connect: setting p->connected=true");
302 p->connected = true;
303 p->client_id = 0; // Will be assigned by server
304
305 log_info("Connected to server at %s:%d (socket=%d)", p->address, p->port, p->socket);
306 log_debug("session_participant_connect: p->connected=%d after setting", p->connected);
307
308 // Invoke callback
309 if (p->callbacks.on_connected) {
310 p->callbacks.on_connected(p, p->client_id, p->user_data);
311 }
312
313 log_debug("session_participant_connect: returning ASCIICHAT_OK");
314 return ASCIICHAT_OK;
315}
bool connected
Currently connected.
Definition participant.c:78
socket_t socket
Connection socket.
Definition participant.c:72
int port
Server port.
Definition participant.c:48
char address[BUFFER_SIZE_SMALL]
Server address.
Definition participant.c:45
bool initialized
Context is initialized.
session_participant_callbacks_t callbacks
Event callbacks.
Definition participant.c:66
void * user_data
User data for callbacks.
Definition participant.c:69
uint32_t client_id
Assigned client ID.
Definition participant.c:81

References session_participant::address, session_participant::callbacks, session_participant::client_id, session_participant::connected, session_participant::initialized, session_participant::port, session_participant::socket, and session_participant::user_data.

Referenced by discovery_session_connect_to_future_host(), and discovery_session_process().

◆ session_participant_create()

session_participant_t * session_participant_create ( const session_participant_config_t *  config)

Definition at line 121 of file participant.c.

121 {
122 log_info("session_participant_create: START - config=%p", config);
123
124 if (!config) {
125 log_error("session_participant_create: config is NULL");
126 SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_create: NULL config");
127 return NULL;
128 }
129
130 // Allocate participant
132 log_info("session_participant_create: allocated p=%p", p);
133
134 // Copy configuration
135 if (config->address) {
136 SAFE_STRNCPY(p->address, config->address, sizeof(p->address));
137 } else {
138 SAFE_STRNCPY(p->address, "127.0.0.1", sizeof(p->address));
139 }
140
141 p->port = config->port > 0 ? config->port : OPT_PORT_INT_DEFAULT;
142 p->encryption_enabled = config->encryption_enabled;
143
144 log_info("session_participant_create: address=%s, port=%d", p->address, p->port);
145
146 if (config->password) {
147 SAFE_STRNCPY(p->password, config->password, sizeof(p->password));
148 }
149
150 if (config->server_key) {
151 SAFE_STRNCPY(p->server_key, config->server_key, sizeof(p->server_key));
152 }
153
154 p->enable_audio = config->enable_audio;
155 p->enable_video = config->enable_video;
156 p->callbacks = config->callbacks;
157 p->user_data = config->user_data;
158
159 // Initialize socket to invalid
160 p->socket = INVALID_SOCKET_VALUE;
161 p->transport = NULL; // No alternative transport initially
162 p->connected = false;
163 p->client_id = 0;
164 p->video_active = false;
165 p->audio_active = false;
166
167 // Initialize settings
169
170 p->initialized = true;
171 log_info("session_participant_create: DONE - returning p=%p (initialized=%d, connected=%d)", p, p->initialized,
172 p->connected);
173 return p;
174}
void session_settings_init(session_settings_t *settings)
Definition settings.c:26
Internal session participant structure.
Definition participant.c:43
session_settings_t settings
Current session settings.
Definition participant.c:90
struct acip_transport * transport
Alternative transport (WebRTC, WebSocket, etc.) - NULL if using socket only.
Definition participant.c:75
bool audio_active
Audio streaming active.
Definition participant.c:87
bool encryption_enabled
Encryption enabled.
Definition participant.c:51
char password[BUFFER_SIZE_SMALL]
Password (if any)
Definition participant.c:54
bool enable_audio
Audio enabled.
Definition participant.c:60
char server_key[BUFFER_SIZE_MEDIUM]
Server key for verification (if any)
Definition participant.c:57
bool video_active
Video streaming active.
Definition participant.c:84
bool enable_video
Video enabled.
Definition participant.c:63

References session_participant::address, session_participant::audio_active, session_participant::callbacks, session_participant::client_id, session_participant::connected, session_participant::enable_audio, session_participant::enable_video, session_participant::encryption_enabled, session_participant::initialized, session_participant::password, session_participant::port, session_participant::server_key, session_settings_init(), session_participant::settings, session_participant::socket, session_participant::transport, session_participant::user_data, and session_participant::video_active.

Referenced by discovery_session_connect_to_future_host(), and discovery_session_process().

◆ session_participant_destroy()

void session_participant_destroy ( session_participant_t p)

Definition at line 176 of file participant.c.

176 {
177 if (!p) {
178 return;
179 }
180
181 // Disconnect if connected
182 if (p->connected) {
184 }
185
186 // Stop capture threads if running
187 if (p->video_capture_running) {
189 }
190 if (p->audio_capture_running) {
192 }
193
194 // Clean up media capture contexts
195 if (p->video_capture) {
197 p->video_capture = NULL;
198 }
199 if (p->audio_capture) {
201 p->audio_capture = NULL;
202 }
203 if (p->opus_encoder) {
205 p->opus_encoder = NULL;
206 }
207
208 // Close socket if open
209 if (p->socket != INVALID_SOCKET_VALUE) {
210 socket_close(p->socket);
211 p->socket = INVALID_SOCKET_VALUE;
212 }
213
214 // Clear sensitive data
215 memset(p->password, 0, sizeof(p->password));
216 memset(p->server_key, 0, sizeof(p->server_key));
217
218 p->initialized = false;
219 SAFE_FREE(p);
220}
void session_audio_destroy(session_audio_ctx_t *ctx)
void session_capture_destroy(session_capture_ctx_t *ctx)
void opus_codec_destroy(opus_codec_t *codec)
Definition opus_codec.c:215
void session_participant_stop_video_capture(session_participant_t *p)
void session_participant_stop_audio_capture(session_participant_t *p)
void session_participant_disconnect(session_participant_t *p)
opus_codec_t * opus_encoder
Opus encoder for audio compression.
session_capture_ctx_t * video_capture
Video capture context (for webcam/file media)
Definition participant.c:93
bool video_capture_running
Video capture thread running flag.
bool audio_capture_running
Audio capture thread running flag.
session_audio_ctx_t * audio_capture
Audio capture context (for microphone)
Definition participant.c:96

References session_participant::audio_capture, session_participant::audio_capture_running, session_participant::connected, session_participant::initialized, opus_codec_destroy(), session_participant::opus_encoder, session_participant::password, session_participant::server_key, session_audio_destroy(), session_capture_destroy(), session_participant_disconnect(), session_participant_stop_audio_capture(), session_participant_stop_video_capture(), session_participant::socket, session_participant::video_capture, and session_participant::video_capture_running.

Referenced by discovery_session_connect_to_future_host(), and discovery_session_destroy().

◆ session_participant_disconnect()

void session_participant_disconnect ( session_participant_t p)

Definition at line 317 of file participant.c.

317 {
318 if (!p || !p->initialized) {
319 return;
320 }
321
322 if (!p->connected) {
323 return;
324 }
325
326 // Stop media streams
327 if (p->video_active) {
329 }
330 if (p->audio_active) {
332 }
333
334 // Close socket
335 if (p->socket != INVALID_SOCKET_VALUE) {
336 socket_close(p->socket);
337 p->socket = INVALID_SOCKET_VALUE;
338 }
339
340 p->connected = false;
341 p->client_id = 0;
342
343 // Invoke callback
344 if (p->callbacks.on_disconnected) {
345 p->callbacks.on_disconnected(p, p->user_data);
346 }
347}
void session_participant_stop_video(session_participant_t *p)
void session_participant_stop_audio(session_participant_t *p)

References session_participant::audio_active, session_participant::callbacks, session_participant::client_id, session_participant::connected, session_participant::initialized, session_participant_stop_audio(), session_participant_stop_video(), session_participant::socket, session_participant::user_data, and session_participant::video_active.

Referenced by discovery_session_process(), and session_participant_destroy().

◆ session_participant_get_client_id()

uint32_t session_participant_get_client_id ( session_participant_t p)

Definition at line 356 of file participant.c.

356 {
357 if (!p || !p->initialized || !p->connected) {
358 return 0;
359 }
360 return p->client_id;
361}

References session_participant::client_id, session_participant::connected, and session_participant::initialized.

◆ session_participant_get_settings()

asciichat_error_t session_participant_get_settings ( session_participant_t p,
session_settings_t *  settings 
)

Definition at line 466 of file participant.c.

466 {
467 if (!p || !p->initialized || !settings) {
468 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_get_settings: invalid parameter");
469 }
470
471 memcpy(settings, &p->settings, sizeof(session_settings_t));
472 return ASCIICHAT_OK;
473}

References session_participant::initialized, and session_participant::settings.

◆ session_participant_get_socket()

socket_t session_participant_get_socket ( session_participant_t p)

Definition at line 363 of file participant.c.

363 {
364 if (!p || !p->initialized) {
365 return INVALID_SOCKET_VALUE;
366 }
367 return p->socket;
368}

References session_participant::initialized, and session_participant::socket.

Referenced by discovery_session_check_host_alive(), and discovery_session_process().

◆ session_participant_get_transport()

acip_transport_t * session_participant_get_transport ( session_participant_t p)

Definition at line 744 of file participant.c.

744 {
745 if (!p || !p->initialized) {
746 return NULL;
747 }
748
749 return p->transport;
750}

References session_participant::initialized, and session_participant::transport.

◆ session_participant_has_transport()

bool session_participant_has_transport ( session_participant_t p)

Definition at line 752 of file participant.c.

752 {
753 if (!p) {
754 return false;
755 }
756
757 return p->transport != NULL;
758}

References session_participant::transport.

◆ session_participant_is_audio_active()

bool session_participant_is_audio_active ( session_participant_t p)

Definition at line 455 of file participant.c.

455 {
456 if (!p || !p->initialized) {
457 return false;
458 }
459 return p->audio_active;
460}

References session_participant::audio_active, and session_participant::initialized.

◆ session_participant_is_connected()

bool session_participant_is_connected ( session_participant_t p)

Definition at line 349 of file participant.c.

349 {
350 if (!p || !p->initialized) {
351 return false;
352 }
353 return p->connected;
354}

References session_participant::connected, and session_participant::initialized.

Referenced by discovery_session_process().

◆ session_participant_is_video_active()

bool session_participant_is_video_active ( session_participant_t p)

Definition at line 411 of file participant.c.

411 {
412 if (!p || !p->initialized) {
413 return false;
414 }
415 return p->video_active;
416}

References session_participant::initialized, and session_participant::video_active.

◆ session_participant_request_settings()

asciichat_error_t session_participant_request_settings ( session_participant_t p,
const session_settings_t *  settings 
)

Definition at line 475 of file participant.c.

475 {
476 if (!p || !p->initialized || !settings) {
477 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_request_settings: invalid parameter");
478 }
479
480 if (!p->connected) {
481 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_request_settings: not connected");
482 }
483
484 // TODO: Send settings update request to server
485
486 return SET_ERRNO(ERROR_NOT_SUPPORTED, "session_participant_request_settings: not implemented yet");
487}

References session_participant::connected, and session_participant::initialized.

◆ session_participant_set_transport()

asciichat_error_t session_participant_set_transport ( session_participant_t p,
acip_transport_t *  transport 
)

Definition at line 726 of file participant.c.

726 {
727 if (!p || !p->initialized) {
728 return SET_ERRNO(ERROR_INVALID_PARAM, "Participant is NULL or not initialized");
729 }
730
731 log_info("session_participant_set_transport: Setting transport=%p (was=%p)", transport, p->transport);
732
733 p->transport = transport;
734
735 if (transport) {
736 log_info("WebRTC transport now active for participant");
737 } else {
738 log_info("WebRTC transport cleared, reverting to socket");
739 }
740
741 return ASCIICHAT_OK;
742}

References session_participant::initialized, and session_participant::transport.

◆ session_participant_start_audio()

asciichat_error_t session_participant_start_audio ( session_participant_t p)

Definition at line 418 of file participant.c.

418 {
419 if (!p || !p->initialized) {
420 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_audio: invalid participant");
421 }
422
423 if (!p->connected) {
424 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio: not connected");
425 }
426
427 if (p->audio_active) {
428 return ASCIICHAT_OK; // Already active
429 }
430
431 if (!p->enable_audio) {
432 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio: audio not enabled");
433 }
434
435 // TODO: Start audio using session_audio_ctx_t
436
437 p->audio_active = true;
438 return ASCIICHAT_OK;
439}

References session_participant::audio_active, session_participant::connected, session_participant::enable_audio, and session_participant::initialized.

◆ session_participant_start_audio_capture()

asciichat_error_t session_participant_start_audio_capture ( session_participant_t p)

Definition at line 647 of file participant.c.

647 {
648 if (!p || !p->initialized) {
649 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_audio_capture: invalid participant");
650 }
651
652 if (!p->connected) {
653 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio_capture: not connected");
654 }
655
656 if (!p->enable_audio) {
657 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio_capture: audio not enabled");
658 }
659
660 if (p->audio_capture_running) {
661 return ASCIICHAT_OK; // Already running
662 }
663
664 // Create audio capture context if not already created
665 if (!p->audio_capture) {
666 p->audio_capture = session_audio_create(false); // false = participant mode (no mixing)
667 if (!p->audio_capture) {
668 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create audio capture context");
669 }
670 }
671
672 // Start audio capture and playback
673 asciichat_error_t err = session_audio_start_duplex(p->audio_capture);
674 if (err != ASCIICHAT_OK) {
675 return SET_ERRNO(err, "Failed to start audio duplex");
676 }
677
678 // Create Opus encoder for audio compression (48kHz, VOIP mode, 24 kbps)
679 if (!p->opus_encoder) {
680 p->opus_encoder = opus_codec_create_encoder(OPUS_APPLICATION_VOIP, 48000, 24000);
681 if (!p->opus_encoder) {
683 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create Opus encoder");
684 }
685 }
686
687 // Spawn audio capture thread
688 p->audio_capture_running = true;
689 if (asciichat_thread_create(&p->audio_capture_thread, participant_audio_capture_thread, p) != 0) {
690 log_error("Failed to spawn audio capture thread");
691 p->audio_capture_running = false;
692 return SET_ERRNO(ERROR_THREAD, "Failed to spawn audio capture thread");
693 }
694
695 log_info("Audio capture started");
696 return ASCIICHAT_OK;
697}
asciichat_error_t session_audio_start_duplex(session_audio_ctx_t *ctx)
void session_audio_stop(session_audio_ctx_t *ctx)
session_audio_ctx_t * session_audio_create(bool is_host)
opus_codec_t * opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate)
Definition opus_codec.c:18
asciichat_thread_t audio_capture_thread
Audio capture thread handle.
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42

References asciichat_thread_create(), session_participant::audio_capture, session_participant::audio_capture_running, session_participant::audio_capture_thread, session_participant::connected, session_participant::enable_audio, session_participant::initialized, opus_codec_create_encoder(), session_participant::opus_encoder, session_audio_create(), session_audio_start_duplex(), and session_audio_stop().

◆ session_participant_start_video()

asciichat_error_t session_participant_start_video ( session_participant_t p)

Definition at line 374 of file participant.c.

374 {
375 if (!p || !p->initialized) {
376 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_video: invalid participant");
377 }
378
379 if (!p->connected) {
380 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video: not connected");
381 }
382
383 if (p->video_active) {
384 return ASCIICHAT_OK; // Already active
385 }
386
387 if (!p->enable_video) {
388 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video: video not enabled");
389 }
390
391 // TODO: Start webcam capture thread using session_capture_ctx_t
392
393 p->video_active = true;
394 return ASCIICHAT_OK;
395}

References session_participant::connected, session_participant::enable_video, session_participant::initialized, and session_participant::video_active.

◆ session_participant_start_video_capture()

asciichat_error_t session_participant_start_video_capture ( session_participant_t p)

Definition at line 586 of file participant.c.

586 {
587 if (!p || !p->initialized) {
588 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_video_capture: invalid participant");
589 }
590
591 if (!p->connected) {
592 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video_capture: not connected");
593 }
594
595 if (!p->enable_video) {
596 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video_capture: video not enabled");
597 }
598
599 if (p->video_capture_running) {
600 return ASCIICHAT_OK; // Already running
601 }
602
603 // Create video capture context if not already created
604 if (!p->video_capture) {
605 session_capture_config_t config = {
606 .type = MEDIA_SOURCE_WEBCAM,
607 .path = "0", // Default device
608 .target_fps = 60,
609 .resize_for_network = true, // Optimize for bandwidth
610 };
612 if (!p->video_capture) {
613 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create video capture context");
614 }
615 }
616
617 // Spawn video capture thread (media source is ready in ctx)
618 p->video_capture_running = true;
619 if (asciichat_thread_create(&p->video_capture_thread, participant_video_capture_thread, p) != 0) {
620 log_error("Failed to spawn video capture thread");
621 p->video_capture_running = false;
622 return SET_ERRNO(ERROR_THREAD, "Failed to spawn video capture thread");
623 }
624
625 log_info("Video capture started");
626 return ASCIICHAT_OK;
627}
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)
asciichat_thread_t video_capture_thread
Video capture thread handle.
Definition participant.c:99

References asciichat_thread_create(), session_participant::connected, session_participant::enable_video, session_participant::initialized, session_capture_create(), session_participant::video_capture, session_participant::video_capture_running, and session_participant::video_capture_thread.

◆ session_participant_stop_audio()

void session_participant_stop_audio ( session_participant_t p)

Definition at line 441 of file participant.c.

441 {
442 if (!p || !p->initialized) {
443 return;
444 }
445
446 if (!p->audio_active) {
447 return;
448 }
449
450 // TODO: Stop audio
451
452 p->audio_active = false;
453}

References session_participant::audio_active, and session_participant::initialized.

Referenced by session_participant_disconnect().

◆ session_participant_stop_audio_capture()

void session_participant_stop_audio_capture ( session_participant_t p)

Definition at line 699 of file participant.c.

699 {
700 if (!p || !p->initialized) {
701 return;
702 }
703
704 if (!p->audio_capture_running) {
705 return;
706 }
707
708 // Signal thread to stop
709 p->audio_capture_running = false;
710
711 // Wait for thread to complete
713
714 // Stop audio streams
715 if (p->audio_capture) {
717 }
718
719 log_info("Audio capture stopped");
720}
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Definition threading.c:46

References asciichat_thread_join(), session_participant::audio_capture, session_participant::audio_capture_running, session_participant::audio_capture_thread, session_participant::initialized, and session_audio_stop().

Referenced by session_participant_destroy().

◆ session_participant_stop_video()

void session_participant_stop_video ( session_participant_t p)

Definition at line 397 of file participant.c.

397 {
398 if (!p || !p->initialized) {
399 return;
400 }
401
402 if (!p->video_active) {
403 return;
404 }
405
406 // TODO: Stop webcam capture thread
407
408 p->video_active = false;
409}

References session_participant::initialized, and session_participant::video_active.

Referenced by session_participant_disconnect().

◆ session_participant_stop_video_capture()

void session_participant_stop_video_capture ( session_participant_t p)

Definition at line 629 of file participant.c.

629 {
630 if (!p || !p->initialized) {
631 return;
632 }
633
634 if (!p->video_capture_running) {
635 return;
636 }
637
638 // Signal thread to stop
639 p->video_capture_running = false;
640
641 // Wait for thread to complete
643
644 log_info("Video capture stopped");
645}

References asciichat_thread_join(), session_participant::initialized, session_participant::video_capture_running, and session_participant::video_capture_thread.

Referenced by session_participant_destroy().