From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/Plugins/Input/in_nsv/Main.cpp | 976 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 976 insertions(+) create mode 100644 Src/Plugins/Input/in_nsv/Main.cpp (limited to 'Src/Plugins/Input/in_nsv/Main.cpp') diff --git a/Src/Plugins/Input/in_nsv/Main.cpp b/Src/Plugins/Input/in_nsv/Main.cpp new file mode 100644 index 00000000..159ee873 --- /dev/null +++ b/Src/Plugins/Input/in_nsv/Main.cpp @@ -0,0 +1,976 @@ +#define PLUGIN_NAME "Nullsoft NSV Decoder" +#define PLUGIN_VERSION L"1.76" + +#include +#include "../Winamp/in2.h" +#include "../nsv/nsvplay/main.h" +#include "resource.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoCharFn.h" +#define NO_IVIDEO_DECLARE +#include "../winamp/wa_ipc.h" +#include "../Winamp/strutil.h" +#include "api.h" +extern In_Module mod; // the output module (filled in near the bottom of this file) + +#define g_hInstance mod.hDllInstance +#define WNDMENU_CAPTION L"Winamp in_nsv" +#define MODAL_ABOUT +#define LOC_MODAL_ABOUT +#include "../nsv/nsvplay/about.h" +#undef g_hInstance + +#include +#include +extern int config_precseek; +extern int config_vidoffs; +extern int config_bufms; +extern int config_prebufms; +extern int config_underunbuf; +extern int config_bufms_f; +extern int config_prebufms_f; +extern int config_underunbuf_f; + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + + +char lastfn[1024] = {0}; // currently playing file (used for getting info on the current file) +static char statusbuf[1024]; + +char stream_url[1024] = {0}; + +ULONGLONG g_bufferstat; +int m_last_bitrate; +void config_read(); +void config_write(); +void config(HWND hwndParent); + +int file_length = 0; // file length, in bytes +// Used for correcting DSP plug-in pitch changes +int paused = 0; // are we paused? +volatile int seek_needed; // if != -1, it is the point that the decode +// thread should seek to, in ms. + +CRITICAL_SECTION g_decoder_cs; +char g_streaminfobuf[512] = {0}; +int g_streaminfobuf_used = 0; + +char error_string[128] = {0}; + +volatile int killDecodeThread = 0; // the kill switch for the decode thread +HANDLE thread_handle = INVALID_HANDLE_VALUE; // the handle to the decode thread + +int has_opened_outmod = 0; +int m_srate = 0; // seek needs this + +int decoders_initted = 0; + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +void process_url(char *url) +{ + lstrcpynA(stream_url, url, sizeof(stream_url)); + + // if (!strncmp(stream_url,"hTtP",4)) + // { + // DWORD dw; + // SendMessageTimeout(mod.hMainWindow,WM_USER,(WPARAM)0,241,SMTO_NORMAL,500,&dw); + // } // I (Tag) removed this support, its annoying. + DWORD_PTR dw = 0; + if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, 241, SMTO_NORMAL, 500, &dw); +} + +char last_title_sent[256] = {0}; + +void process_metadata(char *data, int len) +{ + if (len && *data) + { + char *ld; + int x; + if (len > 4096) return ; + for (x = 0; x < len; x ++) + if (!data[x]) break; + if (x == len) return ; + while ((ld = strstr(data, "='"))) + { + char * n = data; + ld[0] = 0; + ld += 2; + data = strstr(ld, "';"); + if (data) + { + data[0] = 0; + data += 2; + if (!lstrcmpiA(n, "StreamTitle")) + { + lstrcpynA(last_title_sent, ld, sizeof(last_title_sent)); + last_title_sent[sizeof(last_title_sent) - 1] = 0; + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + else if (!lstrcmpiA(n, "StreamUrl")) + { + process_url(ld); + } + } + else break; + } + } +} + +class WA2AudioOutput : public IAudioOutput +{ +public: + WA2AudioOutput(int srate, int nch, int bps) + { + memset(m_stuffbuf, 0, sizeof(m_stuffbuf)); + decode_pos_samples = 0; + m_srate = srate; m_bps = bps; m_nch = nch; + m_open_success = 0; + m_stuffbuf_u = 0; + int maxlat = mod.outMod->Open(srate, nch, bps, -1, -1); + if (maxlat == 0 && strstr(lastfn, "://")) + { + maxlat = -1; + mod.outMod->Close(); // boom + } + if (maxlat >= 0) + { + mod.SetInfo( -1, srate / 1000, nch, 1); + mod.SAVSAInit(maxlat, srate); + mod.VSASetInfo(srate, nch); + mod.outMod->SetVolume( -666); + m_open_success = 1; + has_opened_outmod = 1; + } + } + ~WA2AudioOutput(){} + + int canwrite() + { + int a = mod.outMod->CanWrite(); + if (mod.dsp_isactive() == 1) a /= 2; + return a & ~((m_nch * (m_bps / 8)) - 1); + } // returns bytes writeable + + void write(void *buf, int len) + { + char *b = (char *)buf; + int s = 576 * m_nch * (m_bps / 8); + if (s > sizeof(m_stuffbuf)) s = sizeof(m_stuffbuf); + + while (len > 0) + { + int l = s; + if (!m_stuffbuf_u && len >= s) // straight copy of data + { + int dms = (int) ((decode_pos_samples * (__int64)1000) / (__int64)m_srate); + mod.SAAddPCMData(b, m_nch, m_bps, dms); + mod.VSAAddPCMData(b, m_nch, m_bps, dms); + } + else if (m_stuffbuf_u + len >= s) + { + int dms = (int) (((decode_pos_samples - (m_stuffbuf_u / m_nch / (m_bps / 8))) * (__int64)1000) / (__int64)m_srate); + l = (s - m_stuffbuf_u); + memcpy(m_stuffbuf + m_stuffbuf_u, b, l); + m_stuffbuf_u = 0; + + mod.SAAddPCMData(m_stuffbuf, m_nch, m_bps, dms); + mod.VSAAddPCMData(m_stuffbuf, m_nch, m_bps, dms); + } + else // put all of len into m_stuffbuf + { + memcpy(m_stuffbuf + m_stuffbuf_u, b, len); + m_stuffbuf_u += len; + l = len; + } + + if (l > len)l = len; // this shouldn't happen but we'll leave it here just in case + + decode_pos_samples += (l / m_nch / (m_bps / 8)); + + if (mod.dsp_isactive()) + { + static char sample_buffer[576*2*(16 / 8)*2]; + int spll = l / m_nch / (m_bps / 8); + memcpy(sample_buffer, b, l); + int l2 = l; + if (spll > 0) l2 = mod.dsp_dosamples((short *)sample_buffer, spll, m_bps, m_nch, m_srate) * (m_nch * (m_bps / 8)); + mod.outMod->Write(sample_buffer, l2); + } + else mod.outMod->Write(b, l); + len -= l; + b += l; + } + } + ULONGLONG getwritepos() + { + return (unsigned int) ((decode_pos_samples * 1000) / m_srate); + } + ULONGLONG getpos() + { + if (seek_needed != -1) return seek_needed; + return (unsigned int) ((decode_pos_samples * 1000) / m_srate) + + (mod.outMod->GetOutputTime() - mod.outMod->GetWrittenTime()) - config_vidoffs; + } + void flush(unsigned int newtime) + { + m_stuffbuf_u = 0; + mod.outMod->Flush(newtime); + decode_pos_samples = (((__int64)newtime) * m_srate) / 1000; + } + void pause(int pause) + { + mod.outMod->Pause(pause); + } + int get_open_success() { return m_open_success; } + int isplaying(void) { return mod.outMod->IsPlaying(); } +private: + __int64 decode_pos_samples; // current decoding position, in milliseconds. + int m_nch, m_bps; + int m_open_success; + int m_stuffbuf_u; + char m_stuffbuf[576*2*2]; +}; + +IAudioOutput *PCMOUT_CREATE(unsigned int outfmt[8]) +{ + if (outfmt[1] && outfmt[2] && outfmt[3] && outfmt[0] == NSV_MAKETYPE('P', 'C', 'M', ' ')) + { + WA2AudioOutput *r = new WA2AudioOutput(outfmt[1], outfmt[2], outfmt[3]); + if (r->get_open_success()) return r; + delete r; + } + return NULL; +} + +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +void about(HWND hwndParent) +{ + do_about(hwndParent,WASABI_API_LNG_HINST); +} + +void SetFileExtensions(void) +{ + static char fileExtensionsString[1200] = {0}; // "NSV;NSA\0Nullsoft Audio/Video File (*.NSV;*.NSA)\0" + char* end = 0; + StringCchCopyExA(fileExtensionsString, 1200, "NSV;NSA", &end, 0, 0); + StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_NSA_NSV_FILE), 0, 0, 0); + mod.FileExtensions = fileExtensionsString; +} + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) AGAVE_API_CONFIG = (api_config *)sf->getInterface(); + + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) WASABI_API_MEMMGR = reinterpret_cast(sf->getInterface()); + + // loader so that we can get the localisation service api for use + sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InNSVLangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_NSV_DECODER),PLUGIN_VERSION); + mod.description = (char*)szDescription; + + SetFileExtensions(); + + config_read(); + InitializeCriticalSection(&g_decoder_cs); + return IN_INIT_SUCCESS; +} + +void quit() +{ + Decoders_Quit(); + DeleteCriticalSection(&g_decoder_cs); + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) sf->releaseInterface(AGAVE_API_CONFIG); + + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) sf->releaseInterface(WASABI_API_MEMMGR); +} + +int isourfile(const char *fn) +{ + // used for detecting URL streams.. unused here. + // return !strncmp(fn,"http://",7); to detect HTTP streams, etc + return !_strnicmp( fn, "unsv://", 7 ); +} + +NSVDecoder *m_decoder = 0; +IVideoOutput *m_video_output = 0; + +int g_play_needseek = -1; + +int play(const char *fn) +{ + m_last_bitrate = -1; + g_play_needseek = -1; + last_title_sent[0] = 0; + g_bufferstat = 0; + mod.is_seekable = 0; + has_opened_outmod = 0; + error_string[0] = 0; + if (!decoders_initted) + { + decoders_initted = 1; + char buf[MAX_PATH] = {0}, *p = buf; + GetModuleFileNameA(mod.hDllInstance, buf, sizeof(buf)); + while (p && *p) p++; + while (p && p > buf && *p != '\\') p--; + if (p) *p = 0; + Decoders_Init(buf); + } + + unsigned long thread_id = 0; + + paused = 0; + seek_needed = -1; + EnterCriticalSection(&g_decoder_cs); + if (strstr(fn, "://")) + WASABI_API_LNGSTRING_BUF(IDS_CONNECTING,error_string,128); + else + WASABI_API_LNGSTRING_BUF(IDS_OPENING,error_string,128); + + LeaveCriticalSection(&g_decoder_cs); + + m_video_output = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GET_IVIDEOOUTPUT); + if (!m_video_output) return 1; + + m_video_output->open(0, 0, 0, 0, 0); + + m_decoder = new NSVDecoder(fn, m_video_output, NULL); + lstrcpynA(lastfn, fn, sizeof(lastfn)); + + if (strstr(fn, "://") || !strncmp(fn, "\\\\", 2)) + { + m_decoder->SetPreciseSeeking(config_precseek&2); + m_decoder->SetBuffering(config_bufms, config_prebufms, config_underunbuf); + } + else + { + m_decoder->SetPreciseSeeking(config_precseek&1); + m_decoder->SetBuffering(config_bufms_f, config_prebufms_f, config_underunbuf_f); + } + + // launch decode thread + killDecodeThread = 0; + thread_handle = (HANDLE)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) DecodeThread, NULL, 0, &thread_id); + SetThreadPriority(thread_handle, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + return 0; +} + +// standard pause implementation +void pause() +{ + paused = 1; + // if (has_opened_outmod) mod.outMod->Pause(1); + // else + if (m_decoder) m_decoder->pause(1); +} +void unpause() +{ + paused = 0; + // if (has_opened_outmod) mod.outMod->Pause(0); + //else + if (m_decoder) m_decoder->pause(0); +} +int ispaused() { return paused; } + +// stop playing. +void stop() +{ + g_play_needseek = -1; + + if (thread_handle != INVALID_HANDLE_VALUE) + { + killDecodeThread = 1; + int nTimes = 0; + const int maxTimes = 1000; + while (WaitForSingleObject(thread_handle, 0) == WAIT_TIMEOUT) + { + MSG msg = {0}; + if (PeekMessage(&msg, NULL, 0, 0, 1)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + Sleep(10); + + nTimes++; + if (nTimes == maxTimes) + { +#ifdef WINAMPX + SendStatus( WINAMPX_STATUS_ERROR_KILLING_THREAD, 0 ); +#else + /*MessageBox(mod.hMainWindow, "error asking thread to die!\n", + "error killing decode thread", 0);*/ +#endif + TerminateThread(thread_handle, 0); + break; + } + } + CloseHandle(thread_handle); + thread_handle = INVALID_HANDLE_VALUE; + } + if (has_opened_outmod) mod.outMod->Close(); + g_bufferstat = 0; + has_opened_outmod = 0; + mod.SAVSADeInit(); + + EnterCriticalSection(&g_decoder_cs); + delete(m_decoder); + m_decoder = NULL; +LeaveCriticalSection(&g_decoder_cs); + g_streaminfobuf[0] = 0; +} + +int getlength() +{ + if (m_decoder) + { + int x = m_decoder->getlen(); + if (x != -1) return x; + } + return -1000; +} + +int getoutputtime() +{ + if (g_bufferstat) return (int)g_bufferstat; + EnterCriticalSection(&g_decoder_cs); + if (m_decoder) + { + LeaveCriticalSection(&g_decoder_cs); + return (int)(m_decoder ? m_decoder->getpos() + config_vidoffs : 0); + } + LeaveCriticalSection(&g_decoder_cs); + return 0; +} + +void setoutputtime(int time_in_ms) +{ + seek_needed = time_in_ms; +} + +void setvolume(int volume) { mod.outMod->SetVolume(volume); } + +void setpan(int pan) { mod.outMod->SetPan(pan); } + +int infoDlg(const char *fn, HWND hwnd); + +// this is an odd function. it is used to get the title and/or +// length of a track. +// if filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// if title is NULL, no title is copied into it. +// if length_in_ms is NULL, no length is copied into it. +void getfileinfo(const char *filename, char *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + EnterCriticalSection(&g_decoder_cs); + if (length_in_ms) *length_in_ms = getlength(); + if (title) // get non-path portion.of filename + { + char *p = NULL; + if (m_decoder) + { + p = m_decoder->getTitle(); + } + if (!p) + { + p = lastfn + strlen(lastfn); + while (p && *p != '\\' && p >= lastfn) p--; + p++; + } + while (p && *p == ';') p++; + title[0] = 0; + + if (error_string[0]) + { + StringCchPrintfA(title, GETFILEINFO_TITLE_LENGTH, "[%s] ", error_string); + } + if (!error_string[0] && last_title_sent[0]) + { + StringCchPrintfA(title, GETFILEINFO_TITLE_LENGTH-strlen(title), "%s (%s)", last_title_sent, p); + } + else + lstrcpynA(title + strlen(title), p, FILETITLE_SIZE); + } + LeaveCriticalSection(&g_decoder_cs); + } + else if (1) // some other file + { + if ((length_in_ms || title) && !strstr(filename, "://")) + { + nsv_InBS bs; + nsv_fileHeader hdr = {0, }; + if (length_in_ms) // calculate length + { + *length_in_ms = -1000; // the default is unknown file length (-1000). + } + if (title) // get non path portion of filename + { + const char *p = filename + strlen(filename); + while (p && *p != '\\' && p >= filename) p--; + lstrcpynA(title, ++p, GETFILEINFO_TITLE_LENGTH); + } + + IDataReader *rdr = CreateReader(filename); + + if (rdr) + { + while (!rdr->iseof()) + { + char buf[1024] = {0}; + int l = (int)rdr->read(buf, sizeof(buf)); + if (!l) break; + bs.add(buf, l); + l = nsv_readheader(bs, &hdr); + if (l <= 0) + { + if (!l) + { + if (length_in_ms) *length_in_ms = hdr.file_lenms; + if (title && hdr.metadata) + { + char *t = nsv_getmetadata(hdr.metadata, "TITLE"); + if (t) lstrcpynA(title, t, 1024); + } + } + free(hdr.metadata); + free(hdr.toc); + break; + } + } + delete rdr; + } + // try to parse out lengths + } + } +} + +void eq_set(int on, char data[10], int preamp) +{} + +DWORD WINAPI DecodeThread(LPVOID b) +{ + int last_bpos = -1; + int firstsynch = 0; + ULONGLONG next_status_time = 0; + while (!killDecodeThread) + { + EnterCriticalSection(&g_decoder_cs); + int r = m_decoder->run((int*)&killDecodeThread); + LeaveCriticalSection(&g_decoder_cs); + if (r < 0) + { + if (m_decoder->get_error()) + { + EnterCriticalSection(&g_decoder_cs); + lstrcpynA(error_string, m_decoder->get_error(), sizeof(error_string)); + LeaveCriticalSection(&g_decoder_cs); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + Sleep(200); + } + break; + } + else if (!r) + { + Sleep(1); + int br = m_decoder->getBitrate() / 1000; + if (br != m_last_bitrate) + { + m_last_bitrate = br; + mod.SetInfo(br, -1, -1, -1); + } + + int bpos = m_decoder->getBufferPos(); + if (bpos > 255) + { + ULONGLONG obuf = g_bufferstat; + g_bufferstat = 0; + if (last_bpos >= 0) + { + EnterCriticalSection(&g_decoder_cs); + error_string[0] = 0; + LeaveCriticalSection(&g_decoder_cs); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + last_bpos = -1; + int csa = mod.SAGetMode(); + if (csa && obuf) + { + char tempdata[75*2] = {0, }; + mod.SAAdd(tempdata, (int)++obuf, (csa == 3) ? 0x80000003 : csa); + } + } + } + else + { + if (!g_bufferstat) + { + if (!has_opened_outmod) mod.SAVSAInit(10, 44100); + + g_bufferstat = m_decoder->getpos() + 1; + } + + if (bpos != last_bpos) + { + last_bpos = bpos; + EnterCriticalSection(&g_decoder_cs); + StringCchPrintfA(error_string, 128, WASABI_API_LNGSTRING(IDS_BUFFER_X), (bpos*100) / 256); + LeaveCriticalSection(&g_decoder_cs); + int csa = mod.SAGetMode(); + char tempdata[2*75] = {0, }; + int x; + if (csa&1) + { + for (x = 0; x < bpos*75 / 256; x ++) + { + tempdata[x] = x * 16 / 75; + } + } + if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + x = 0; + while (x < bpos*75 / 256) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + if (csa == 4) + { + tempdata[0] = tempdata[1] = (bpos * 127 / 256); + } + + if (csa) mod.SAAdd(tempdata, (int)++g_bufferstat, (csa == 3) ? 0x80000003 : csa); + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + } + + if (GetTickCount64() > next_status_time || GetTickCount64() < next_status_time - 5000) + { + char statusbuf[1024] = {0}; + EnterCriticalSection(&g_decoder_cs); + g_streaminfobuf[0] = 0; + + if (g_streaminfobuf_used) + { + char *outp = g_streaminfobuf; + size_t size = 512; + const char *p = m_decoder->getServerHeader("server"); + if (!p) p = m_decoder->getServerHeader("icy-notice2"); + + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_SERVER), p); + + p = m_decoder->getServerHeader("content-type"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_CONTENT_TYPE), p); + + p = m_decoder->getServerHeader("content-length"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_CONTENT_LENGTH), p); + + p = m_decoder->getServerHeader("icy-name"); + if (p && strlen(p) < sizeof(g_streaminfobuf) - (outp - g_streaminfobuf) - 32) + StringCchPrintfExA(outp, size, &outp, &size, 0, "%s: %s\r\n", WASABI_API_LNGSTRING(IDS_STREAM_NAME), p); + } + + lstrcpynA(statusbuf, "NSV: ", 1024); + { // codecs + char *sb = statusbuf; + size_t size = 1024; + + int l = m_decoder->getlen(); + if (l > 0) + { + l /= 1000; + if (l >= 3600) + { + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d:", l / 3600); + l %= 3600; + StringCchPrintfExA(sb, size, &sb, &size, 0, "%02d:%02d", l / 60, l % 60); + } + else + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d:%02d", l / 60, l % 60); + } + + int a = (m_decoder->getBitrate() + 500) / 1000; + if (a) + { + if (strlen(statusbuf) > 5) + StringCchCatExA(sb, size, " @ ", &sb, &size, 0); + StringCchPrintfExA(sb, size, &sb, &size, 0, "%d%s", a, WASABI_API_LNGSTRING(IDS_KBPS)); + } + + if (strlen(statusbuf) > 5) + StringCchCatExA(sb, size, ", ", &sb, &size, 0); + + char *p = statusbuf + strlen(statusbuf); + m_decoder->getVideoDesc(p); + if (p && !*p) StringCchCopyExA(p, size, "?, ", &p, &size, 0); + else if (!strncmp(p, "NONE", 4)) *p = 0; + else StringCchCatExA(p, size, ", ", &p, &size, 0); + + p = statusbuf + strlen(statusbuf); + m_decoder->getAudioDesc(p); + if (p && !*p) StringCchCopyExA(p, size, "?", &p, &size, 0); + else if (!strncmp(p, "NONE", 4)) *p = 0; + } + LeaveCriticalSection(&g_decoder_cs); + m_video_output->extended(VIDUSER_SET_INFOSTRING, (intptr_t)statusbuf, 0); + + next_status_time = GetTickCount64() + 2500; + } + } + else + { + if (!firstsynch) + { + if (m_decoder->canseek()) + { + mod.is_seekable = 1; + if (g_play_needseek >= 0) seek_needed = g_play_needseek; + g_play_needseek = -1; + } + firstsynch++; + PostMessage(mod.hMainWindow, WM_USER, 0, 243); + } + } + if (seek_needed >= 0) + { + EnterCriticalSection(&g_decoder_cs); + m_decoder->seek(seek_needed); + seek_needed = -1; + LeaveCriticalSection(&g_decoder_cs); + } + } + if (!killDecodeThread) + { + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + m_decoder->CloseVideo(); + return 0; +} + +// module definition. +In_Module mod = + { + IN_VER_RET, // defined in IN2.H + "nullsoft(in_nsv.dll)", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, // visualization calls filled in by winamp + + 0, 0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0 // out_mod filled in by winamp + }; + +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +extern "C" +{ + __declspec( dllexport ) In_Module * winampGetInModule2() + { + return &mod; + } + + wchar_t lastextfn[1024] = {0}; + // Keep track of file timestamp for file system change notification handling + FILETIME last_write_time = {0, 0}; + static int valid; + static nsv_fileHeader hdr; + + bool isFileChanged(const wchar_t* file) + { + WIN32_FIND_DATAW FindFileData = {0}; + HANDLE hFind = FindFirstFileW(file, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) + return true; + FindClose(hFind); + + if ((last_write_time.dwHighDateTime == + FindFileData.ftLastWriteTime.dwHighDateTime) && + (last_write_time.dwLowDateTime == + FindFileData.ftLastWriteTime.dwLowDateTime)) + { + return false; + } + return true; + } + + __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, int destlen) + { + if (!_stricmp(data, "type")) + { + if (!fn || !fn[0] || _wcsicmp(PathFindExtensionW(fn), L".nsa")) // if extension is NOT nsa + lstrcpyn(dest, L"1", destlen); //video + else + lstrcpyn(dest, L"0", destlen); // audio + return 1; + } + + if (!fn || (fn && !fn[0])) + return 0; + + if (!_stricmp(data, "family")) + { + int pID = -1; + LPCWSTR e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"NSV", -1)) pID = IDS_FAMILY_STRING_NSV; + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"NSA", -1)) pID = IDS_FAMILY_STRING_NSA; + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + //the file name differs from the last file + //name but we need to check the time stamp too + if (_wcsicmp(fn, lastextfn) || isFileChanged(fn) || HasFileTimeChanged(fn)) + { + free(hdr.metadata); + memset(&hdr, 0, sizeof(hdr)); + valid = 0; + + lstrcpyn(lastextfn, fn, ARRAYSIZE(lastextfn)); + + nsv_InBS bs; + + IDataReader *rdr = CreateReader(AutoCharFn(lastextfn)); + + if (rdr) + { + while (!rdr->iseof()) + { + char buf[1024] = {0}; + int l = (int)rdr->read(buf, sizeof(buf)); + if (!l) break; + bs.add(buf, l); + l = nsv_readheader(bs, &hdr); + if (l <= 0) + { + free(hdr.toc); + if (!l) + { + valid = 1; + + //Save time stamp + WIN32_FIND_DATAW FindFileData = {0}; + HANDLE hFind = FindFirstFileW(fn, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) + { + last_write_time.dwHighDateTime = NULL; + last_write_time.dwLowDateTime = NULL; + } + else + { + last_write_time.dwHighDateTime = FindFileData.ftLastWriteTime.dwHighDateTime; + last_write_time.dwLowDateTime = FindFileData.ftLastWriteTime.dwLowDateTime; + FindClose(hFind); + } + break; + } + break; + } + } + delete rdr; + } + } + + dest[0] = 0; + + if (!valid) + { + return 0; + } + + if (!_stricmp(data, "length")) + { + if (hdr.file_lenms > 0) + { + StringCchPrintfW(dest, destlen, L"%d", hdr.file_lenms); + } + } + else if (hdr.metadata) + { + const char *t = nsv_getmetadata(hdr.metadata, (char*)data); + if (t) lstrcpyn(dest, AutoWide(t), destlen); + } + + return 1; + } +} \ No newline at end of file -- cgit