#define AL_LOG_SECTION "desktop" #include #ifdef CAMU_NO_MINIAUDIO_BACKENDS // @TODO: audio_null is incomplete in that it never actually reads from the buffers. // This is an issue in CAMU_DIRECT_MODE because eventually the server-side packet_pool // will be starved by pending audio packets. #define DESKTOP_NULL_AUDIO #endif #ifdef DESKTOP_NULL_AUDIO #include "../mixer/audio_null.h" #else #include "../mixer/audio_miniaudio.h" #endif #if defined CAMU_RENDERER_MOMO #include "../render/renderer_momo.h" #elif defined CAMU_RENDERER_LIBPLACEBO #include "../render/renderer_libplacebo.h" #endif #include "../util/print_time.h" #include "desktop.h" #ifdef CAMU_SCREEN_DEBUG_KEY #include "input_simulator.h" #endif static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque) { struct camu_desktop *c = (struct camu_desktop *)userdata; struct camu_mixer *mixer = &c->mixer; struct camu_screen *scr = &c->scr; switch (op) { case CAMU_SINK_ADD_BUFFER: switch (type) { case CAMU_SINK_AUDIO: { struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; camu_mixer_add_buffer(mixer, buf); log_debug("Audio buffer added."); break; } case CAMU_SINK_VIDEO: { struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; camu_screen_add_buffer(scr, buf); log_debug("Video buffer added."); break; } } break; case CAMU_SINK_REMOVE_BUFFER: switch (type) { case CAMU_SINK_AUDIO: { struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; camu_mixer_remove_buffer(mixer, buf); log_debug("Audio buffer removed."); break; } case CAMU_SINK_VIDEO: { struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; camu_screen_remove_buffer(scr, buf); log_debug("Video buffer removed."); break; } } break; case CAMU_SINK_START: switch (type) { case CAMU_SINK_AUDIO: log_info("Starting audio."); camu_mixer_resume(mixer); break; case CAMU_SINK_VIDEO: log_info("Starting video."); camu_screen_set_state(scr, CAMU_SCREEN_PLAYING); camu_screen_wake(scr); break; } break; case CAMU_SINK_STOP: switch (type) { case CAMU_SINK_AUDIO: log_info("Stopping audio."); camu_mixer_pause(mixer); break; case CAMU_SINK_VIDEO: log_info("Stopping video."); camu_screen_set_state(scr, CAMU_SCREEN_PAUSED); break; } break; case CAMU_SINK_REFRESH_VIDEO: camu_screen_force_refresh(scr); break; case CAMU_SINK_CLEAR: switch (type) { case CAMU_SINK_AUDIO: camu_mixer_clear(mixer); break; case CAMU_SINK_VIDEO: camu_screen_clear(scr); break; } break; case CAMU_SINK_MOCK_CLOSE: c->should_quit = 1; camu_screen_wake(&c->scr); break; case CAMU_SINK_EXIT: camu_sink_close(&c->sink); if (c->exit_callback) { c->exit_callback(c->userdata, c); } break; } return CAMU_SINK_OK; } static char status[64]; static void log_sink_status(struct camu_desktop *c) { struct camu_sink *sink = &c->sink; struct camu_sink_entry *current = camu_sink_get_current(sink); if (!current) { camu_sink_return_current(sink); goto notplaying; } u64 pts = camu_clock_get_last_pts(¤t->clock) * 1000000u; u64 duration = current->client.duration; pts = MIN(duration, pts); bool paused = current->paused; camu_sink_return_current(sink); u64 remaining = duration - pts; if (remaining == 0) { goto notplaying; } bool show_hour = duration > 60u * 60u * 1000000u; s32 offset = al_snprintf(status, sizeof(status), paused ? "|| " : "|> "); offset += al_snprintf(status + offset, sizeof(status) - offset, "["); offset += camu_print_time(status + offset, sizeof(status) - offset, pts, show_hour, 2); offset += al_snprintf(status + offset, sizeof(status) - offset, "/"); offset += camu_print_time(status + offset, sizeof(status) - offset, duration, show_hour, 2); offset += al_snprintf(status + offset, sizeof(status) - offset, "]"); status[offset] = '\0'; log_info("%s, window size: %ux%u, volume: %.2f.", status, c->scr.width, c->scr.height, c->mixer.volume); return; notplaying: log_info("Not playing, window size: %ux%u, volume %.2f.", c->scr.width, c->scr.height, c->mixer.volume); } static void screen_callback(void *userdata, u8 op, void *opaque) { struct camu_desktop *c = (struct camu_desktop *)userdata; switch (op) { case CAMU_SCREEN_SET_VOLUME: { f32 volume = *(f32 *)opaque; if (volume == -1.f) { volume = (c->mixer.volume == 0.f) ? 1.f : 0.f; } camu_mixer_set_volume(&c->mixer, volume); log_info("Volume: %f.", volume); break; } case CAMU_SCREEN_OFFSET_VOLUME: { f32 volume = camu_mixer_offset_volume(&c->mixer, *(f32 *)opaque); log_info("Volume: %f.", volume); break; } case CAMU_SCREEN_SKIP: camu_sink_skip(&c->sink, *(s32 *)opaque); break; case CAMU_SCREEN_TOGGLE_PAUSE: camu_sink_toggle_pause(&c->sink); break; case CAMU_SCREEN_PERCENT_SEEK: camu_sink_seek(&c->sink, opaque, CAMU_SEEK_PERCENT); break; case CAMU_SCREEN_RELATIVE_SEEK: camu_sink_seek(&c->sink, opaque, CAMU_SEEK_RELATIVE); break; case CAMU_SCREEN_RESEEK: camu_sink_reseek(&c->sink); break; case CAMU_SCREEN_SHUFFLE: camu_sink_shuffle(&c->sink); break; case CAMU_SCREEN_STATUS: log_sink_status(c); break; #ifdef CAMU_SCREEN_DEBUG_KEY case CAMU_SCREEN_DEBUG: if (camu_input_simulator_running()) { camu_input_simulator_stop(); } else { camu_input_simulator_run(&c->sink); } break; #endif case CAMU_SCREEN_CLOSE: c->should_quit = 1; break; } } bool camu_desktop_open(struct camu_desktop *c, char *window_name) { c->scr.callback = screen_callback; c->scr.userdata = c; if (!(camu_screen_init(&c->scr) && camu_screen_create_window(&c->scr, window_name))) { log_error("Failed to create window."); return false; } #if defined CAMU_RENDERER_MOMO c->renderer = camu_renderer_momo_create(); #elif defined CAMU_RENDERER_LIBPLACEBO c->renderer = camu_renderer_libplacebo_create(); #endif if (!camu_screen_create_renderer(&c->scr, c->renderer)) { log_error("Failed to create renderer."); return false; } // Need to render twice for the window to show early. c->renderer->render(c->renderer, &c->scr, true); c->renderer->render(c->renderer, &c->scr, true); #ifdef DESKTOP_NULL_AUDIO if (!camu_mixer_init(&c->mixer, (struct camu_audio *)&audio_plugin_null)) { #else if (!camu_mixer_init(&c->mixer, camu_audio_miniaudio_create())) { #endif return false; } if (!c->mixer.audio->configure_stream(c->mixer.audio, NULL)) { return false; } camu_mixer_pick_format(&c->mixer, &c->mixer.fmt); c->should_quit = 0; c->exit_callback = NULL; c->userdata = NULL; return true; } bool camu_desktop_connect(struct camu_desktop *c, str *name, struct nn_event_loop *loop, u8 type, str *addr, u16 port) { c->loop = loop; camu_sink_init(&c->sink, c->loop, &c->mixer, c->renderer); al_str_from(&c->sink.default_list, "default"); c->sink.callback = sink_callback; c->sink.userdata = c; return camu_sink_connect(&c->sink, name, type, addr, port); } bool camu_desktop_tick(struct camu_desktop *c) { bool force; if (camu_screen_tick(&c->scr, &force) && !c->should_quit) { if (!c->renderer->render(c->renderer, &c->scr, force)) { log_error("render() failed, forcing exit."); c->should_quit = 1; } } return !c->should_quit; } void camu_desktop_stop(struct camu_desktop *c) { #ifdef CAMU_SCREEN_DEBUG_KEY if (camu_input_simulator_running()) { camu_input_simulator_stop(); } #endif // We have already stopped ticking the screen at this point. camu_screen_clear(&c->scr); camu_sink_stop(&c->sink); } void camu_desktop_free(struct camu_desktop *c) { camu_sink_free(&c->sink); al_str_free(&c->sink.default_list); camu_mixer_close(&c->mixer); if (c->renderer) { c->renderer->free(&c->renderer); } camu_screen_close(&c->scr); }