Mercurial > foo_out_sdl
view foosdk/sdk/foobar2000/foo_sample/playback_stream_capture.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line source
#include "stdafx.h" #include <helpers/writer_wav.h> #include <SDK/playback_stream_capture.h> namespace { // private classes in anon namespace typedef CWavWriter wav_writer; typedef wavWriterSetup_t wav_writer_setup; static pfc::string8 g_outputPath; static wav_writer g_wav_writer; class playback_stream_capture_callback_impl : public playback_stream_capture_callback { public: void on_chunk(const audio_chunk & chunk) override { PFC_ASSERT(core_api::is_main_thread()); try { // writing files in main thread is not pretty, but good enough for our demo auto & abort = fb2k::noAbort; if (g_wav_writer.is_open() && g_wav_writer.get_spec() != chunk.get_spec()) { g_wav_writer.finalize(abort); } if (!g_wav_writer.is_open() && ! core_api::is_shutting_down() ) { wav_writer_setup setup; setup.initialize(chunk, 16, false, false); pfc::string_formatter fn; fn << "capture-" << pfc::print_guid(pfc::createGUID()) << ".wav"; pfc::string_formatter path = g_outputPath; path.add_filename( fn ); // pretty method to add file path components with auto inserted delimiter g_wav_writer.open(path, setup, abort); } g_wav_writer.write(chunk, abort); } catch(std::exception const & e) { FB2K_console_formatter() << "Playback stream capture error: " << e; // FIX ME handle this in a pretty manner, likely inaccessible output folder or out of disk space } } }; static playback_stream_capture_callback_impl g_callback; static bool g_active = false; static void FlushCapture() { if (g_active) { g_wav_writer.finalize(fb2k::noAbort); } } static void StopCapture() { if ( g_active ) { g_active = false; playback_stream_capture::get()->remove_callback(&g_callback); g_wav_writer.finalize(fb2k::noAbort); } } static void StartCapture() { PFC_ASSERT( g_outputPath.length() > 0 ); if (!g_active && !core_api::is_shutting_down()) { g_active = true; playback_stream_capture::get()->add_callback(&g_callback); } } // Forcibly stop capture when fb2k is shutting down class initquit_psc : public initquit { public: void on_quit() override { PFC_ASSERT( core_api::is_shutting_down() ); StopCapture(); } }; // Handle playback stop events to split output WAV files class play_callback_psc : public play_callback_static { public: unsigned get_flags() override { return flag_on_playback_stop; } void on_playback_stop(play_control::t_stop_reason p_reason) override { // Terminate the current stream FlushCapture(); } void on_playback_starting(play_control::t_track_command p_command,bool p_paused) override {} void on_playback_new_track(metadb_handle_ptr p_track) override {} void on_playback_seek(double p_time) override {} void on_playback_pause(bool p_state) override {} void on_playback_edited(metadb_handle_ptr p_track) override {} void on_playback_dynamic_info(const file_info & p_info) override {} void on_playback_dynamic_info_track(const file_info & p_info) override {} void on_playback_time(double p_time) override {} void on_volume_change(float p_new_val) override {} }; // pretty modern macro for service_factory_single_t<> FB2K_SERVICE_FACTORY( initquit_psc ); FB2K_SERVICE_FACTORY( play_callback_psc ); } static void startCaptureDialogReply( fb2k::arrayRef location ) { if ( g_active ) return; // already capturing if ( location.is_empty() ) return; if ( location->size() != 1 ) return; auto obj = location->itemAt(0); fsItemFolderPtr folder; fb2k::stringRef strFolder; if ( strFolder &= obj ) { // OK } else if ( folder &= obj ) { strFolder = folder->canonicalPath(); } if ( strFolder.is_valid() ) { g_outputPath = strFolder->c_str(); StartCapture(); } } void ToggleCapture() { #ifdef _WIN32 // Block modal dialog recursions. // Folder picker below is a modal dialog, don't ever call it if there's another modal dialog in progress. // Also prevents this function from recursing into itself if someone manages to hit the menu item while already picking folder. // This will bump whatever modal dialog already exists, so the user has some idea why this was refused. if ( !ModalDialogPrologue() ) return; if (g_active) { StopCapture(); } else { const HWND wndParent = core_api::get_main_window(); modal_dialog_scope scope(wndParent); // we can't have a handle to the modal dialog, but parent handle is good enough if (uBrowseForFolder(wndParent, "Choose output folder", g_outputPath)) { StartCapture(); } } #else if ( g_active ) { StopCapture(); } else { auto dlg = fb2k::fileDialog::get()->setupOpenFolder(); dlg->setTitle( "Choose output folder" ); dlg->run( fb2k::fileDialogNotify::create( startCaptureDialogReply ) ); } #endif } bool IsCaptureRunning() { return g_active; }
