aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_flv
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Input/in_flv')
-rw-r--r--Src/Plugins/Input/in_flv/AMFDispatch.cpp238
-rw-r--r--Src/Plugins/Input/in_flv/AMFDispatch.h28
-rw-r--r--Src/Plugins/Input/in_flv/AMFObject.cpp319
-rw-r--r--Src/Plugins/Input/in_flv/AMFObject.h182
-rw-r--r--Src/Plugins/Input/in_flv/BackgroundDownloader.cpp129
-rw-r--r--Src/Plugins/Input/in_flv/BackgroundDownloader.h19
-rw-r--r--Src/Plugins/Input/in_flv/ExtendedInfo.cpp29
-rw-r--r--Src/Plugins/Input/in_flv/FLVAudioHeader.cpp34
-rw-r--r--Src/Plugins/Input/in_flv/FLVAudioHeader.h31
-rw-r--r--Src/Plugins/Input/in_flv/FLVCOM.cpp189
-rw-r--r--Src/Plugins/Input/in_flv/FLVCOM.h37
-rw-r--r--Src/Plugins/Input/in_flv/FLVHeader.cpp41
-rw-r--r--Src/Plugins/Input/in_flv/FLVHeader.h18
-rw-r--r--Src/Plugins/Input/in_flv/FLVMetadata.cpp112
-rw-r--r--Src/Plugins/Input/in_flv/FLVMetadata.h24
-rw-r--r--Src/Plugins/Input/in_flv/FLVProcessor.cpp1
-rw-r--r--Src/Plugins/Input/in_flv/FLVProcessor.h40
-rw-r--r--Src/Plugins/Input/in_flv/FLVReader.cpp211
-rw-r--r--Src/Plugins/Input/in_flv/FLVReader.h49
-rw-r--r--Src/Plugins/Input/in_flv/FLVStreamHeader.cpp36
-rw-r--r--Src/Plugins/Input/in_flv/FLVStreamHeader.h27
-rw-r--r--Src/Plugins/Input/in_flv/FLVUtil.h90
-rw-r--r--Src/Plugins/Input/in_flv/FLVVideoHeader.cpp24
-rw-r--r--Src/Plugins/Input/in_flv/FLVVideoHeader.h32
-rw-r--r--Src/Plugins/Input/in_flv/FileProcessor.cpp201
-rw-r--r--Src/Plugins/Input/in_flv/FileProcessor.h43
-rw-r--r--Src/Plugins/Input/in_flv/PlayThread.cpp703
-rw-r--r--Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp34
-rw-r--r--Src/Plugins/Input/in_flv/ProgressiveProcessor.h16
-rw-r--r--Src/Plugins/Input/in_flv/StreamProcessor.cpp112
-rw-r--r--Src/Plugins/Input/in_flv/StreamProcessor.h28
-rw-r--r--Src/Plugins/Input/in_flv/VideoThread.cpp291
-rw-r--r--Src/Plugins/Input/in_flv/VideoThread.h21
-rw-r--r--Src/Plugins/Input/in_flv/api__in_flv.h12
-rw-r--r--Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h62
-rw-r--r--Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h67
-rw-r--r--Src/Plugins/Input/in_flv/in_flv.rc85
-rw-r--r--Src/Plugins/Input/in_flv/in_flv.sln30
-rw-r--r--Src/Plugins/Input/in_flv/in_flv.vcxproj295
-rw-r--r--Src/Plugins/Input/in_flv/in_flv.vcxproj.filters158
-rw-r--r--Src/Plugins/Input/in_flv/main.cpp432
-rw-r--r--Src/Plugins/Input/in_flv/main.h27
-rw-r--r--Src/Plugins/Input/in_flv/resource.h23
-rw-r--r--Src/Plugins/Input/in_flv/svc_flvdecoder.h52
-rw-r--r--Src/Plugins/Input/in_flv/version.rc239
45 files changed, 4671 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flv/AMFDispatch.cpp b/Src/Plugins/Input/in_flv/AMFDispatch.cpp
new file mode 100644
index 00000000..0bd8af76
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/AMFDispatch.cpp
@@ -0,0 +1,238 @@
+#include "AMFDispatch.h"
+
+AMFDispatch::AMFDispatch(AMFMixedArray *array)
+{
+ object=array;
+ if (object)
+ object->AddRef();
+ refCount=1;
+}
+
+AMFDispatch::~AMFDispatch()
+{
+ if (object)
+ object->Release();
+}
+
+STDMETHODIMP AMFDispatch::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG AMFDispatch::AddRef(void)
+{
+ return InterlockedIncrement((volatile LONG *)&refCount);
+}
+
+ULONG AMFDispatch::Release(void)
+{
+ ULONG count = InterlockedDecrement((volatile LONG *)&refCount);
+ if (count == 0)
+ delete this;
+ return count;
+}
+
+enum
+{
+ DISP_AMF_DEBUGPRINT,
+ DISP_AMF_MAX,
+};
+
+#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT AMFDispatch::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("DebugPrint", DISP_AMF_DEBUGPRINT)
+ if (object)
+ {
+ //size_t index = object->array.getPosition(rgszNames[i]);
+ size_t index = 0;
+ for (auto it = object->array.begin(); it != object->array.end(); it++, index++)
+ {
+ if (wcscmp(it->first.c_str(), rgszNames[i]) == 0)
+ {
+ break;
+ }
+ }
+
+ if (index != object->array.size())
+ {
+ rgdispid[i] = (DISPID)index + DISP_AMF_MAX;
+ continue;
+ }
+ }
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT AMFDispatch::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT AMFDispatch::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+static void AMFType_To_Variant(AMFType *obj, VARIANT *pvarResult)
+{
+ VariantInit(pvarResult);
+ switch(obj->type)
+ {
+ case AMFType::TYPE_DOUBLE: // double
+ {
+ AMFDouble *cast_obj = static_cast<AMFDouble *>(obj);
+ V_VT(pvarResult) = VT_R8;
+ V_R8(pvarResult) = cast_obj->val;
+ }
+ break;
+ case AMFType::TYPE_BOOL: // bool
+ {
+ AMFBoolean *cast_obj = static_cast<AMFBoolean *>(obj);
+ V_VT(pvarResult) = VT_BOOL;
+ V_BOOL(pvarResult) = cast_obj->boolean;
+ }
+ break;
+ case AMFType::TYPE_MOVIE: // movie (basically just a URL)
+ case AMFType::TYPE_STRING: // string
+ {
+ AMFString *cast_obj = static_cast<AMFString *>(obj);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = SysAllocString(cast_obj->str);
+ }
+ break;
+ case AMFType::TYPE_LONG_STRING: // string
+ {
+ AMFLongString *cast_obj = static_cast<AMFLongString *>(obj);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = SysAllocString(cast_obj->str);
+ }
+ break;
+ case AMFType::TYPE_MIXEDARRAY:
+ {
+ AMFMixedArray *cast_obj = static_cast<AMFMixedArray *>(obj);
+ V_VT(pvarResult) = VT_DISPATCH;
+ V_DISPATCH(pvarResult) = new AMFDispatch(cast_obj);
+ }
+ break;
+ case AMFType::TYPE_DATE:
+ {
+ AMFTime *cast_obj = static_cast<AMFTime *>(obj);
+ V_VT(pvarResult) = VT_DATE;
+ V_DATE(pvarResult) = cast_obj->val;
+ }
+ break;
+ case AMFType::TYPE_ARRAY:
+ {
+ AMFArray *cast_obj = static_cast<AMFArray *>(obj);
+ SAFEARRAYBOUND rgsabound[1];
+ rgsabound[0].lLbound = 0;
+ rgsabound[0].cElements = (ULONG)cast_obj->array.size();
+ SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound);
+ VARIANT **data;
+ SafeArrayAccessData(psa, (void **)&data);
+ for (size_t i=0;i!=cast_obj->array.size();i++)
+ {
+ AMFType_To_Variant(cast_obj->array[i], data[i]);
+ }
+ SafeArrayUnaccessData(psa);
+ V_VT(pvarResult) = VT_ARRAY;
+ V_ARRAY(pvarResult) = psa;
+ }
+ break;
+ }
+}
+
+HRESULT AMFDispatch::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ if (pvarResult)
+ VariantInit(pvarResult);
+
+ switch(dispid)
+ {
+ case DISP_AMF_DEBUGPRINT:
+ {
+ wchar_t debugstring[4096]=L"";
+ wchar_t *str = debugstring;
+ size_t len = 4096;
+ object->DebugPrint(1, str, len);
+ V_VT(pvarResult) = VT_BSTR;
+ V_BSTR(pvarResult) = SysAllocString(debugstring);
+ }
+ return S_OK;
+ }
+ size_t index = dispid - DISP_AMF_MAX;
+ if (index >= object->array.size())
+ return DISP_E_MEMBERNOTFOUND;
+
+ //AMFType *obj = object->array.at(index).second;
+ AMFType* obj = 0;
+ auto it = object->array.begin();
+ while (index--)
+ {
+ it++;
+ }
+ if (it != object->array.end())
+ {
+ obj = it->second;
+ }
+
+ if (!obj)
+ return S_OK;
+
+ switch(obj->type)
+ {
+ case AMFType::TYPE_DOUBLE:
+ case AMFType::TYPE_BOOL:
+ case AMFType::TYPE_STRING:
+ case AMFType::TYPE_MIXEDARRAY:
+ case AMFType::TYPE_ARRAY:
+ AMFType_To_Variant(obj, pvarResult);
+ return S_OK;
+
+ case AMFType::TYPE_OBJECT: // object
+ // TODO
+ return DISP_E_TYPEMISMATCH;
+ case AMFType::TYPE_NULL: // null
+ return S_OK;
+ case AMFType::TYPE_REFERENCE: // reference
+ return DISP_E_TYPEMISMATCH;
+ case AMFType::TYPE_TERMINATOR:
+ // TODO?
+ return DISP_E_TYPEMISMATCH;
+ case AMFType::TYPE_DATE: // date
+ return DISP_E_TYPEMISMATCH;
+ case AMFType::TYPE_LONG_STRING: // long string
+ return DISP_E_TYPEMISMATCH;
+ case AMFType::TYPE_XML: // XML
+ return DISP_E_TYPEMISMATCH;
+ default:
+ return DISP_E_TYPEMISMATCH;
+
+ }
+ return S_OK;
+}
diff --git a/Src/Plugins/Input/in_flv/AMFDispatch.h b/Src/Plugins/Input/in_flv/AMFDispatch.h
new file mode 100644
index 00000000..3a593875
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/AMFDispatch.h
@@ -0,0 +1,28 @@
+#pragma once
+/*
+Ben Allison
+April 30, 2008
+
+This class implements an IDispatch interface around an AMFObject
+*/
+#include <oaidl.h>
+#include "AMFObject.h"
+class AMFDispatch : public IDispatch
+{
+public:
+ AMFDispatch(AMFMixedArray *array);
+ ~AMFDispatch();
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+private:
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ volatile ULONG refCount;
+ AMFMixedArray *object;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/AMFObject.cpp b/Src/Plugins/Input/in_flv/AMFObject.cpp
new file mode 100644
index 00000000..a0c5be38
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/AMFObject.cpp
@@ -0,0 +1,319 @@
+#include "AMFObject.h"
+#include <strsafe.h>
+void AMFMixedArray::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ AMFTypeList::iterator itr;
+ StringCchCopyEx(str, len, L"Mixed Array [\n", &str, &len, 0);
+ for (itr=array.begin();itr!=array.end();itr++)
+ {
+ for (int i=0;i<spaces;i++)
+ StringCchCopyEx(str, len, L" ", &str, &len,0);
+
+ if (itr->first.c_str() != 0 && itr->first.c_str()[0])
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%s: ", itr->first.c_str());
+ if (itr->second)
+ itr->second->DebugPrint(spaces+1, str, len);
+ else
+ StringCchCopyEx(str, len, L"(null)\n", &str, &len, 0);
+ }
+ StringCchCopyEx(str, len, L"]\n", &str, &len, 0);
+
+}
+
+size_t AMFMixedArray::Read(uint8_t *data, size_t size)
+{
+ size_t read = 0;
+ uint32_t maxIndex = FLV::Read32(data);
+// TODO? array.reserve(maxIndex);
+ data += 4;
+ size -= 4;
+ read += 4;
+
+ while (size)
+ {
+ AMFString amfString;
+ size_t skip = amfString.Read(data, size);
+ data += skip;
+ size -= skip;
+ read += skip;
+
+ uint8_t type = *data;
+ data++;
+ size--;
+ read++;
+ AMFType *obj = MakeObject(type);
+ if (obj)
+ {
+ obj->type = type;
+ size_t skip = obj->Read(data, size);
+ data += skip;
+ size -= skip;
+ read += skip;
+
+ array[amfString.str] = obj;
+ }
+ else
+ break;
+
+ if (type == TYPE_TERMINATOR)
+ break;
+
+ }
+
+ return read;
+}
+
+AMFMixedArray::~AMFMixedArray()
+{
+ for (AMFTypeList::iterator itr=array.begin();itr!=array.end();itr++)
+ {
+ delete itr->second;
+ }
+}
+
+
+void AMFObj::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchCopyEx(str, len, L"Object (TODO)\n", &str, &len, 0);
+}
+
+size_t AMFObj::Read(uint8_t *data, size_t size)
+{
+ size_t read = 0;
+ while (size)
+ {
+ AMFString amfString;
+ size_t skip = amfString.Read(data, size);
+ data += skip;
+ size -= skip;
+ read += skip;
+
+ uint8_t type = *data;
+ data++;
+ size--;
+ read++;
+ AMFType *obj = MakeObject(type);
+ if (obj)
+ {
+ obj->type = type;
+ size_t skip = obj->Read(data, size);
+ data += skip;
+ size -= skip;
+ read += skip;
+ }
+ else
+ return false;
+
+ if (type == TYPE_TERMINATOR)
+ break;
+ }
+ return read;
+}
+
+
+void AMFArray::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+
+ StringCchCopyEx(str, len, L"Array [\n", &str, &len, 0);
+ for (size_t i=0;i!=array.size();i++)
+ {
+ for (int s=0;s<spaces;s++)
+ StringCchCopyEx(str, len, L" ", &str, &len,0);
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%u: ", i);
+ array[i]->DebugPrint(spaces+1, str, len);
+ }
+ StringCchCopyEx(str, len, L"]\n", &str, &len, 0);
+}
+
+size_t AMFArray::Read(uint8_t *data, size_t size)
+{
+ size_t read = 0;
+ uint32_t arrayLength = FLV::Read32(data);
+ array.reserve(arrayLength);
+ data += 4;
+ read += 4;
+ size -= 4;
+
+ for (uint32_t i=0;i!=arrayLength;i++)
+ {
+ uint8_t type = *data;
+ data++;
+ read++;
+ size--;
+
+ AMFType *obj = MakeObject(type);
+ size_t skip = obj->Read(data, size);
+ //array[i]=obj;
+ array.push_back(obj);
+ data += skip;
+ read += skip;
+ size -= skip;
+ }
+
+ return read;
+}
+
+AMFArray::~AMFArray()
+{
+ for (size_t i=0;i!=array.size();i++)
+ {
+ delete array[i];
+ }
+}
+
+/* --- String --- */
+AMFString::AMFString() : str(0)
+{}
+AMFString::~AMFString()
+{
+ free(str);
+}
+
+void AMFString::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str);
+}
+
+size_t AMFString::Read(uint8_t *data, size_t size)
+{
+ if (size < 2)
+ return 0;
+
+ unsigned __int16 strlength = FLV::Read16(data);
+ data += 2;
+ size -= 2;
+
+ if (strlength > size)
+ return 0;
+
+ char *utf8string = (char *)calloc(strlength, sizeof(char));
+ memcpy(utf8string, data, strlength);
+
+ int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0);
+ str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t));
+
+ MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen);
+ str[wideLen] = 0;
+ free(utf8string);
+
+ return strlength + 2;
+}
+
+/* --- Long String --- */
+AMFLongString::AMFLongString() : str(0)
+{}
+AMFLongString::~AMFLongString()
+{
+ free(str);
+}
+
+void AMFLongString::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str);
+}
+
+size_t AMFLongString::Read(uint8_t *data, size_t size)
+{
+ if (size < 4)
+ return 0;
+
+ uint32_t strlength = FLV::Read32(data);
+ data += 4;
+ size -= 4;
+
+ if (strlength > size)
+ return 0;
+
+ char *utf8string = (char *)calloc(strlength, sizeof(char));
+ memcpy(utf8string, data, strlength);
+
+ int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0);
+ str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t));
+
+ MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen);
+ str[wideLen] = 0;
+ free(utf8string);
+
+ return strlength + 4;
+}
+
+/* --- Double --- */
+void AMFDouble::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%f\n", val);
+}
+
+/* --- Boolean --- */
+void AMFBoolean::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", boolean?L"true":L"false");
+}
+
+/* --- Time --- */
+static size_t MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen)
+{
+ SYSTEMTIME sysTime;
+ tm *newtime = _localtime64(&convertTime);
+ dest[0] = 0; // so we can bail out easily
+ if (newtime)
+ {
+ sysTime.wYear = (WORD)(newtime->tm_year + 1900);
+ sysTime.wMonth = (WORD)(newtime->tm_mon + 1);
+ sysTime.wDayOfWeek = (WORD)newtime->tm_wday;
+ sysTime.wDay = (WORD)newtime->tm_mday;
+ sysTime.wHour = (WORD)newtime->tm_hour;
+ sysTime.wMinute = (WORD)newtime->tm_min;
+ sysTime.wSecond = (WORD)newtime->tm_sec;
+ sysTime.wMilliseconds = 0;
+
+ int charsWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, (int)destlen);
+ if (charsWritten)
+ {
+ size_t dateSize = charsWritten-1;
+ dest += dateSize;
+ destlen -= dateSize;
+ if (destlen)
+ {
+ *dest++ = L' ';
+ destlen--;
+ dateSize++;
+ }
+
+ int charsWritten2 = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, (int)destlen);
+ if (charsWritten2)
+ {
+ dateSize+=(charsWritten2-1);
+ }
+ return dateSize;
+ }
+ }
+
+ return 1;
+
+}
+
+void AMFTime::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ size_t written = MakeDateString((__time64_t)val, str, len);
+ str+=written;
+ len-=written;
+ if (len>=2)
+ {
+ str[0]='\n';
+ str[1]=0;
+ len--;
+ str++;
+ }
+}
+
+/* --- Terminator --- */
+void AMFTerminator::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchCopyEx(str, len, L"array terminator\n", &str, &len, 0);
+}
+
+/* --- Reference --- */
+void AMFReference::DebugPrint(int spaces, wchar_t *&str, size_t &len)
+{
+ StringCchPrintfEx(str, len, &str, &len, 0, L"%u\n", val);
+}
diff --git a/Src/Plugins/Input/in_flv/AMFObject.h b/Src/Plugins/Input/in_flv/AMFObject.h
new file mode 100644
index 00000000..bb423a9c
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/AMFObject.h
@@ -0,0 +1,182 @@
+#ifndef NULLSOFT_AMFOBJECT_H
+#define NULLSOFT_AMFOBJECT_H
+
+#include "FLVUtil.h"
+#include <windows.h>
+#include <map>
+#include <string>
+#include <vector>
+#include <time.h>
+
+
+class AMFType // no, not bowling, ActionScript Message Format
+{
+public:
+ AMFType() : refCount(1) {}
+ virtual ~AMFType() {}
+ virtual size_t Read(uint8_t *data, size_t size)=0; // returns number of bytes read, 0 on failure
+ uint8_t type;
+ virtual void DebugPrint(int spaces, wchar_t *&str, size_t &len)=0;
+ enum
+ {
+ TYPE_DOUBLE = 0x0,
+ TYPE_BOOL = 0x1,
+ TYPE_STRING = 0x2,
+ TYPE_OBJECT = 0x3,
+ TYPE_MOVIE = 0x4,
+ TYPE_NULL = 0x5,
+ TYPE_REFERENCE = 0x7,
+ TYPE_MIXEDARRAY = 0x8,
+ TYPE_TERMINATOR = 0x9,
+ TYPE_ARRAY = 0xA,
+ TYPE_DATE = 0xB,
+ TYPE_LONG_STRING = 0xC,
+ TYPE_XML = 0xF,
+ };
+
+ ULONG AddRef(void)
+ {
+ return InterlockedIncrement((volatile LONG *)&refCount);
+ }
+
+ ULONG Release(void)
+ {
+ ULONG count = InterlockedDecrement((volatile LONG *)&refCount);
+ if (count == 0)
+ delete this;
+ return count;
+ }
+private:
+ ULONG refCount;
+};
+
+class AMFString : public AMFType
+{
+public:
+ AMFString();
+ ~AMFString();
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size);
+ wchar_t *str;
+};
+
+class AMFLongString : public AMFType
+{
+public:
+ AMFLongString();
+ ~AMFLongString();
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size);
+ wchar_t *str;
+};
+
+class AMFObj : public AMFType
+{
+public:
+ size_t Read(uint8_t *data, size_t size);
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+};
+
+class AMFArray : public AMFType
+{
+public:
+ size_t Read(uint8_t *data, size_t size);
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ ~AMFArray();
+ std::vector<AMFType*> array;
+};
+
+class AMFMixedArray : public AMFType
+{
+public:
+ size_t Read(uint8_t *data, size_t size);
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ ~AMFMixedArray();
+ typedef std::map<std::wstring, AMFType *> AMFTypeList;
+ AMFTypeList array;
+};
+
+class AMFDouble : public AMFType
+{
+public:
+void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size)
+ {
+ if (size < 8)
+ return 0;
+
+ val = FLV::ReadDouble(data);
+ return 8;
+ }
+ double val;
+};
+
+class AMFTime : public AMFType
+{
+public:
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size)
+ {
+ if (size < 10)
+ return 0;
+
+ val = FLV::ReadDouble(data);
+ data+=8;
+
+ offset = FLV::Read16(data);
+
+ return 10;
+ }
+ double val; // same epoch as time_t, just stored as a double instead of an unsigned int
+ int16_t offset; // offset in minutes from UTC. presumably from the encoding machine's timezone
+};
+
+class AMFBoolean : public AMFType
+{
+public:
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size)
+ {
+ if (size < 1)
+ return 0;
+ boolean = !!data[0];
+ return 1;
+ }
+
+ bool boolean;
+};
+
+class AMFTerminator : public AMFType
+{
+public:
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size)
+ {
+ return 0;
+ }
+};
+
+class AMFReference : public AMFType
+{
+public:
+ void DebugPrint(int spaces, wchar_t *&str, size_t &len);
+ size_t Read(uint8_t *data, size_t size)
+ {
+ if (size < 2)
+ return 0;
+ val = FLV::Read16(data);
+ return 2;
+ }
+ uint16_t val;
+};
+
+inline double AMFGetDouble(AMFType *obj)
+{
+ if (obj->type == 0x0)
+ return ((AMFDouble *)obj)->val;
+
+ return 0;
+}
+
+AMFType *MakeObject(uint8_t type);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp
new file mode 100644
index 00000000..bcb3b716
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp
@@ -0,0 +1,129 @@
+#include "Main.h"
+#include "BackgroundDownloader.h"
+#include "..\..\..\Components\wac_network\wac_network_http_receiver_api.h"
+#include "api__in_flv.h"
+#include "api/service/waservicefactory.h"
+#include "../nu/AutoChar.h"
+
+#include <strsafe.h>
+
+#define HTTP_BUFFER_SIZE 65536
+
+// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
+static const GUID internetConfigGroupGUID =
+{
+ 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
+};
+
+
+static void SetUserAgent( api_httpreceiver *http )
+{
+ char agent[ 256 ] = { 0 };
+ StringCchPrintfA( agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString() );
+ http->addheader( agent );
+}
+
+static int FeedHTTP( api_httpreceiver *http, Downloader::DownloadCallback *callback, int *downloaded )
+{
+ char downloadedData[ HTTP_BUFFER_SIZE ] = { 0 };
+ int result = 0;
+ int downloadSize = http->get_bytes( downloadedData, HTTP_BUFFER_SIZE );
+
+ *downloaded = downloadSize;
+ if ( downloadSize )
+ {
+ result = callback->OnData( downloadedData, downloadSize );
+ }
+
+ return result;
+}
+
+static void RunDownload( api_httpreceiver *http, Downloader::DownloadCallback *callback )
+{
+ int ret;
+ int downloaded = 0;
+ do
+ {
+ ret = http->run();
+ Sleep( 55 );
+ do
+ {
+ if ( FeedHTTP( http, callback, &downloaded ) != 0 )
+ return;
+ } while ( downloaded == HTTP_BUFFER_SIZE );
+ } while ( ret == HTTPRECEIVER_RUN_OK );
+
+ // finish off the data
+ do
+ {
+ if ( FeedHTTP( http, callback, &downloaded ) != 0 )
+ return;
+ } while ( downloaded );
+}
+
+
+bool Downloader::Download(const char *url, Downloader::DownloadCallback *callback, uint64_t startPosition)
+{
+ api_httpreceiver *http = 0;
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID);
+ if (sf) http = (api_httpreceiver *)sf->getInterface();
+
+ if (!http)
+ return false;
+
+ int use_proxy = 1;
+ bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
+ if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
+ use_proxy = 0;
+
+ const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0;
+
+ http->AllowCompression();
+ http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL);
+ if (startPosition > 0)
+ {
+ char temp[128] = {0};
+ StringCchPrintfA(temp, 128, "Range: bytes=%d-", startPosition);
+ http->addheader(temp);
+ }
+ SetUserAgent(http);
+ http->connect(url);
+ int ret;
+
+ do
+ {
+ Sleep(55);
+ ret = http->run();
+ if (ret == -1) // connection failed
+ break;
+
+ // ---- check our reply code ----
+ int replycode = http->getreplycode();
+ switch (replycode)
+ {
+ case 0:
+ case 100:
+ break;
+ case 200:
+ {
+ if (callback->OnConnect(http) != 0)
+ {
+ sf->releaseInterface(http);
+ return false;
+ }
+
+ RunDownload(http, callback);
+ sf->releaseInterface(http);
+ return true;
+ }
+ break;
+ default:
+ sf->releaseInterface(http);
+ return false;
+ }
+ }
+ while (ret == HTTPRECEIVER_RUN_OK);
+ //const char *er = http->geterrorstr();
+ sf->releaseInterface(http);
+ return false;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.h b/Src/Plugins/Input/in_flv/BackgroundDownloader.h
new file mode 100644
index 00000000..01b08e9a
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <bfc/platform/types.h>
+
+class api_httpreceiver;
+
+class Downloader
+{
+public:
+ class DownloadCallback
+ {
+ public:
+ virtual int OnConnect(api_httpreceiver *http)=0;
+ virtual int OnData(void *buffer, size_t bufferSize)=0;
+ };
+
+
+ bool Download(const char *url, DownloadCallback *callback, uint64_t startPosition = 0);
+
+}; \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/ExtendedInfo.cpp b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp
new file mode 100644
index 00000000..c7a807f0
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp
@@ -0,0 +1,29 @@
+#include <bfc/platform/types.h>
+#include <windows.h>
+#include <strsafe.h>
+#include "api__in_flv.h"
+#include "resource.h"
+
+extern "C" __declspec(dllexport)
+ int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen)
+{
+ if (!_stricmp(data, "type"))
+ {
+ dest[0]='1';
+ dest[1]=0;
+ return 1;
+ }
+ else if (!_stricmp(data, "family"))
+ {
+ int len;
+ const wchar_t *p;
+ if (!fn || !fn[0]) return 0;
+ len = lstrlenW(fn);
+ if (len < 4 || L'.' != fn[len - 4]) return 0;
+ p = &fn[len - 3];
+ if (!_wcsicmp(p, L"FLV") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1;
+ return 0;
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp
new file mode 100644
index 00000000..5042bf42
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp
@@ -0,0 +1,34 @@
+#include "FLVAudioHeader.h"
+
+/*
+(c) 2006 Nullsoft, Inc.
+Author: Ben Allison benski@nullsoft.com
+*/
+
+/*
+soundType 1 bit (byte & 0x01) >> 0 0: mono, 1: stereo
+soundSize 1 bit (byte & 0x02)>> 1 0: 8-bit, 2: 16-bit
+soundRate 2 bits (byte & 0x0C) >> 2 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz
+soundFormat 4 bits (byte & 0xf0) >> 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser
+*/
+
+bool FLVAudioHeader::Read(unsigned __int8 *data, size_t size)
+{
+ if (size < 1)
+ return false; // header size too small
+
+
+ unsigned __int8 byte = data[0];
+
+ stereo = !!((byte & 0x01) >> 0);
+ bits = ((byte & 0x02) >> 1) ? 16 : 8;
+ switch ((byte & 0x0C) >> 2)
+ {
+ case 0: sampleRate = 5512; break;
+ case 1: sampleRate = 11025; break;
+ case 2: sampleRate = 22050; break;
+ case 3: sampleRate = 44100; break;
+ }
+ format = (byte & 0xf0) >> 4;
+ return true;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.h b/Src/Plugins/Input/in_flv/FLVAudioHeader.h
new file mode 100644
index 00000000..8a749a4a
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.h
@@ -0,0 +1,31 @@
+#ifndef NULLSOFT_FLVAUDIOHEADER_H
+#define NULLSOFT_FLVAUDIOHEADER_H
+
+namespace FLV
+{
+ const int AUDIO_FORMAT_PCM = 0;
+ const int AUDIO_FORMAT_ADPCM = 1;
+ const int AUDIO_FORMAT_MP3 = 2;
+ const int AUDIO_FORMAT_PCM_LE = 3; // little endian
+ const int AUDIO_FORMAT_NELLYMOSER_16KHZ = 4;
+ const int AUDIO_FORMAT_NELLYMOSER_8KHZ = 5;
+ const int AUDIO_FORMAT_NELLYMOSER = 6;
+ const int AUDIO_FORMAT_A_LAW = 7;
+ const int AUDIO_FORMAT_MU_LAW = 8;
+ const int AUDIO_FORMAT_AAC = 10;
+ const int AUDIO_FORMAT_MP3_8KHZ = 14;
+
+};
+
+class FLVAudioHeader
+{
+public:
+ bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header
+
+ // attributes, consider these read-only
+ bool stereo;
+ int bits;
+ int sampleRate;
+ int format;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVCOM.cpp b/Src/Plugins/Input/in_flv/FLVCOM.cpp
new file mode 100644
index 00000000..5d6b4cc5
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVCOM.cpp
@@ -0,0 +1,189 @@
+#include "FLVCOM.h"
+#include "AMFDispatch.h"
+FLVCOM flvCOM;
+extern bool mute;
+
+static HANDLE DuplicateCurrentThread()
+{
+ HANDLE fakeHandle = GetCurrentThread();
+ HANDLE copiedHandle = 0;
+ HANDLE processHandle = GetCurrentProcess();
+ DuplicateHandle(processHandle, fakeHandle, processHandle, &copiedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ return copiedHandle;
+}
+
+enum
+{
+ DISP_FLV_REGISTER_CALLBACK,
+ DISP_FLV_UNREGISTER_CALLBACK,
+ DISP_FLV_SETMUTE,
+};
+
+#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT FLVCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("RegisterCallback", DISP_FLV_REGISTER_CALLBACK)
+ CHECK_ID("UnregisterCallback", DISP_FLV_UNREGISTER_CALLBACK)
+ CHECK_ID("SetMute", DISP_FLV_SETMUTE)
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT FLVCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT FLVCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT FLVCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ if (pvarResult)
+ VariantInit(pvarResult);
+
+ switch (dispid)
+ {
+ case DISP_FLV_SETMUTE:
+ {
+ if (pdispparams->rgvarg[0].boolVal)
+ mute = true;
+ else
+ mute = false;
+ }
+ return S_OK;
+ case DISP_FLV_REGISTER_CALLBACK:
+ {
+ IDispatch *callback = pdispparams->rgvarg[0].pdispVal;
+
+ callbacks.push_back(DispatchCallbackInfo(callback, GetCurrentThreadId(), DuplicateCurrentThread()));
+ return S_OK;
+ }
+ break;
+ case DISP_FLV_UNREGISTER_CALLBACK:
+ {
+ IDispatch *callback = pdispparams->rgvarg[0].pdispVal;
+ size_t numCallbacks = callbacks.size();
+ while (numCallbacks--)
+ {
+ if (callbacks[numCallbacks].dispatch == callback)
+ {
+ CloseHandle(callbacks[numCallbacks].threadHandle);
+ callbacks.erase(callbacks.begin() + numCallbacks);
+ }
+ }
+ return S_OK;
+ }
+ break;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP FLVCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG FLVCOM::AddRef(void)
+{
+ return 0;
+}
+
+ULONG FLVCOM::Release(void)
+{
+ return 0;
+}
+
+static void CallDispatchMethod(IDispatch *dispatch, DISPPARAMS &params, OLECHAR *name)
+{
+ try
+ {
+ unsigned int ret;
+ DISPID dispid;
+ if (SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &dispid)))
+ dispatch->Invoke(dispid, GUID_NULL, 0, DISPATCH_METHOD, &params, 0, 0, &ret);
+ }
+ catch (...)
+ {}
+}
+
+struct APCdata
+{
+ IDispatch *disp;
+ wchar_t *name;
+ AMFDispatch *amf;
+};
+
+static VOID CALLBACK MetadataAPC(ULONG_PTR param)
+{
+ APCdata *data = (APCdata *)param;
+
+ DISPPARAMS params;
+ VARIANT argument;
+ params.cArgs = 1;
+ params.cNamedArgs = 0;
+ params.rgdispidNamedArgs = 0;
+ params.rgvarg = &argument;
+ VariantInit(&argument);
+ V_VT(&argument) = VT_DISPATCH;
+ V_DISPATCH(&argument) = data->amf;
+
+ CallDispatchMethod(data->disp, params, data->name);
+
+ data->disp->Release();
+ data->amf->Release();
+ free(data->name);
+ delete data;
+}
+
+void FLVCOM::MetadataCallback(FLVMetadata::Tag *tag)
+{
+ if (!callbacks.empty())
+ {
+ AMFDispatch *disp = new AMFDispatch(tag->parameters); // we're newing this we can refcount
+
+ DWORD curThreadId = GetCurrentThreadId();
+ for (size_t i = 0;i != callbacks.size();i++)
+ {
+ APCdata *data = new APCdata;
+ data->disp = callbacks[i].dispatch;
+ data->disp->AddRef();
+ data->name = _wcsdup(tag->name.str);
+ data->amf = disp;
+ data->amf->AddRef();
+
+ if (curThreadId == callbacks[i].threadId)
+ MetadataAPC((ULONG_PTR)data);
+ else
+ {
+ if (callbacks[i].threadHandle)
+ QueueUserAPC(MetadataAPC, callbacks[i].threadHandle, (ULONG_PTR)data);
+ }
+ }
+ disp->Release();
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVCOM.h b/Src/Plugins/Input/in_flv/FLVCOM.h
new file mode 100644
index 00000000..e521bf80
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVCOM.h
@@ -0,0 +1,37 @@
+#pragma once
+/*
+Ben Allison
+April 30, 2008
+*/
+#include <oaidl.h>
+#include <vector>
+#include "FLVMetadata.h"
+
+struct DispatchCallbackInfo
+{
+ DispatchCallbackInfo() : dispatch(0), threadId(0), threadHandle(0) {}
+ DispatchCallbackInfo(IDispatch *d, DWORD id, HANDLE _handle) : dispatch(d), threadId(id), threadHandle(_handle) {}
+ IDispatch *dispatch;
+ DWORD threadId;
+ HANDLE threadHandle;
+};
+
+class FLVCOM : public IDispatch
+{
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ void MetadataCallback(FLVMetadata::Tag *tag);
+private:
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ std::vector<DispatchCallbackInfo> callbacks;
+};
+
+extern FLVCOM flvCOM; \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVHeader.cpp b/Src/Plugins/Input/in_flv/FLVHeader.cpp
new file mode 100644
index 00000000..3b5bc1ee
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVHeader.cpp
@@ -0,0 +1,41 @@
+#include "FLVHeader.h"
+#include "FLVUtil.h"
+/*
+(c) 2006 Nullsoft, Inc.
+Author: Ben Allison benski@nullsoft.com
+*/
+
+#define FLV_BITMASK_AUDIO 0x4
+#define FLV_BITMASK_VIDEO 0x1
+
+/*
+FLV Header spec
+
+Signature - uint8[3] - must equal "FLV"
+Version - uint8 - only known version is 1
+Flags - uint8 - bitmask, 4 is audio, 1 is video
+Offset - uint32 - total size of header (9), big endian
+*/
+
+
+
+
+bool FLVHeader::Read(uint8_t *data, size_t size)
+{
+ if (size < 9)
+ return false; // too small to be an FLV header
+
+ if (data[0] != 'F' || data[1] != 'L' || data[2] != 'V')
+ return false; // invalid signature
+
+ version = data[3];
+
+ hasAudio = !!(data[4] & FLV_BITMASK_AUDIO);
+ hasVideo = data[4] & FLV_BITMASK_VIDEO;
+
+ headerSize = FLV::Read32(&data[5]);
+ if (headerSize != 9)
+ return false;
+
+ return true;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVHeader.h b/Src/Plugins/Input/in_flv/FLVHeader.h
new file mode 100644
index 00000000..9fc283c5
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVHeader.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_FLVHEADER_H
+#define NULLSOFT_FLVHEADER_H
+
+#include <bfc/platform/types.h>
+
+class FLVHeader
+{
+public:
+ FLVHeader() : version(0), hasAudio(0), hasVideo(0), headerSize(0) {}
+ bool Read(uint8_t *data, size_t size); // size must be >=9, returns "true" if this was a valid header
+
+ // attributes, consider these read-only
+ uint8_t version;
+ bool hasAudio, hasVideo;
+ uint32_t headerSize;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.cpp b/Src/Plugins/Input/in_flv/FLVMetadata.cpp
new file mode 100644
index 00000000..b98e801b
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVMetadata.cpp
@@ -0,0 +1,112 @@
+#include "FLVMetadata.h"
+#include "FLVUtil.h"
+#include <windows.h>
+
+/*
+(c) 2006 Nullsoft, Inc.
+Author: Ben Allison benski@nullsoft.com
+*/
+
+/*
+
+type - uint8 -
+length - uint16
+
+*/
+
+
+AMFType *MakeObject(uint8_t type)
+{
+ switch(type)
+ {
+ case AMFType::TYPE_DOUBLE: // double
+ return new AMFDouble;
+ case AMFType::TYPE_BOOL: // bool
+ return new AMFBoolean;
+ case AMFType::TYPE_STRING: // string
+ return new AMFString;
+ case AMFType::TYPE_OBJECT: // object
+ return new AMFObj;
+ case AMFType::TYPE_MOVIE: // movie (basically just a URL)
+ return new AMFString;
+ case AMFType::TYPE_NULL: // null
+ return 0;
+ case AMFType::TYPE_REFERENCE: // reference
+ return 0;
+ case AMFType::TYPE_MIXEDARRAY:
+ return new AMFMixedArray;
+ case AMFType::TYPE_TERMINATOR:
+ return new AMFTerminator;
+ case AMFType::TYPE_ARRAY:
+ return new AMFArray;
+ case AMFType::TYPE_DATE: // date
+ return new AMFTime;
+ case AMFType::TYPE_LONG_STRING: // long string
+ return new AMFLongString;
+ case AMFType::TYPE_XML: // XML
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+FLVMetadata::FLVMetadata()
+{
+}
+
+FLVMetadata::~FLVMetadata()
+{
+ for ( FLVMetadata::Tag *tag : tags )
+ delete tag;
+}
+
+bool FLVMetadata::Read(uint8_t *data, size_t size)
+{
+ // TODO: there can be multiple name/value pairs so we could read them all
+ while(size)
+ {
+ uint8_t type=*data;
+ data++;
+ size--;
+
+ if (type == 0 && size >= 2 && data[0] == 0 && data[1] == AMFType::TYPE_TERMINATOR) // check for terminator
+ return true; // array is done
+
+ if (type != AMFType::TYPE_STRING) // first entry is a string, verify this
+ return false; // malformed, lets bail
+
+ FLVMetadata::Tag *tag = new FLVMetadata::Tag;
+
+ // read name
+ size_t skip = tag->name.Read(data, size);
+ data+=skip;
+ size-=skip;
+
+ type=*data;
+ data++;
+ size--;
+
+ if (type != AMFType::TYPE_MIXEDARRAY) // second entry is an associative array, verify this
+ {
+ delete tag;
+ return false; // malformed, lets bail
+ }
+
+ tag->parameters = new AMFMixedArray; // we're new'ing this because we need to reference count
+ skip = tag->parameters->Read(data, size);
+ data+=skip;
+ size-=skip;
+ tags.push_back(tag);
+ }
+
+ return true;
+}
+
+FLVMetadata::Tag::Tag() : parameters(0)
+{
+}
+ FLVMetadata::Tag::~Tag()
+ {
+ if (parameters)
+ parameters->Release();
+ } \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.h b/Src/Plugins/Input/in_flv/FLVMetadata.h
new file mode 100644
index 00000000..e67f26bc
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVMetadata.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_FLVMETADATA_H
+#define NULLSOFT_FLVMETADATA_H
+
+#include "AMFObject.h"
+#include <vector>
+
+class FLVMetadata
+{
+public:
+ FLVMetadata();
+ ~FLVMetadata();
+ bool Read(uint8_t *data, size_t size);
+ struct Tag
+ {
+ Tag();
+ ~Tag();
+
+ AMFString name;
+ AMFMixedArray *parameters; // needs to be pointer so we can refcount
+ };
+ std::vector<Tag*> tags;
+
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.cpp b/Src/Plugins/Input/in_flv/FLVProcessor.cpp
new file mode 100644
index 00000000..961549d8
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVProcessor.cpp
@@ -0,0 +1 @@
+#include "FLVProcessor.h"
diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.h b/Src/Plugins/Input/in_flv/FLVProcessor.h
new file mode 100644
index 00000000..061ff08b
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVProcessor.h
@@ -0,0 +1,40 @@
+#pragma once
+#include <bfc/platform/types.h>
+#include "FLVStreamHeader.h"
+#include "FLVHeader.h"
+
+struct FrameData
+{
+ FLVStreamHeader header;
+ uint64_t location;
+ bool keyFrame;
+};
+
+enum
+{
+ FLV_OK=0,
+ FLV_NEED_MORE_DATA=1,
+ FLV_ERROR=-1,
+
+ FLVPROCESSOR_WRITE_OK=0,
+ FLVPROCESSOR_WRITE_ERROR=1,
+ FLVPROCESSOR_WRITE_WAIT=2,
+};
+
+class FLVProcessor
+{
+public:
+ virtual ~FLVProcessor() {}
+ virtual int Write(void *data, size_t datalen, size_t *written) = 0;
+ virtual int Process()=0;
+ virtual uint64_t Seek(uint64_t position)=0;
+ virtual size_t Read(void *data, size_t bytes)=0;
+ virtual uint64_t GetProcessedPosition()=0;
+ virtual bool GetFrame(size_t frameIndex, FrameData &frameData)=0;
+ virtual uint32_t GetMaxTimestamp()=0;
+ virtual bool GetPosition(int time_in_ms, size_t *frameIndex, bool needsVideoKeyFrame)=0;
+ virtual bool IsStreaming()=0;
+ virtual FLVHeader *GetHeader() = 0;
+};
+
+
diff --git a/Src/Plugins/Input/in_flv/FLVReader.cpp b/Src/Plugins/Input/in_flv/FLVReader.cpp
new file mode 100644
index 00000000..c756cc70
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVReader.cpp
@@ -0,0 +1,211 @@
+#include "FLVReader.h"
+#include "FLVHeader.h"
+#include "FLVStreamheader.h"
+#include "BackgroundDownloader.h"
+#include "../nu/AutoChar.h"
+#include "FileProcessor.h"
+#include "ProgressiveProcessor.h"
+#include "StreamProcessor.h"
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+#include <shlwapi.h>
+
+FLVReader::FLVReader(const wchar_t *_url)
+{
+ processor = 0;
+ url = _wcsdup(_url);
+ end_of_stream=false;
+
+ killswitch=false;
+ DWORD threadId;
+ flvThread=CreateThread(NULL, NULL, ParserThreadStub, this, 0, &threadId);
+ SetThreadPriority(flvThread, THREAD_PRIORITY_BELOW_NORMAL);
+}
+
+FLVReader::~FLVReader()
+{
+ free(url);
+ delete processor;
+}
+
+uint64_t FLVReader::Seek(uint64_t position)
+{
+ if (processor)
+ return processor->Seek(position);
+ else
+ return -1;
+}
+
+size_t FLVReader::Read(void *data, size_t bytes)
+{
+ if (processor)
+ return processor->Read(data, bytes);
+ else
+ return 0;
+}
+
+uint64_t FLVReader::GetProcessedPosition()
+{
+ if (processor)
+ return processor->GetProcessedPosition();
+ else
+ return 0;
+}
+
+void FLVReader::ProcessFile()
+{
+ if (!processor)
+ return;
+
+ while (processor->Process() == FLV_OK)
+ {
+ if (killswitch)
+ return ;
+ }
+}
+
+int FLVReader::OnConnect(api_httpreceiver *http)
+{
+ if (http->content_length())
+ processor = new ProgressiveProcessor;
+ else
+ processor = new StreamProcessor;
+ return 0;
+}
+
+int FLVReader::OnData(void *data, size_t datalen)
+{
+ if (!processor)
+ return 1;
+
+ bool needSleep=false;
+ while (datalen)
+ {
+ if (killswitch)
+ return 1;
+
+ if (needSleep)
+ {
+ Sleep(10);
+ needSleep=false;
+ }
+
+ size_t written=0;
+ switch (processor->Write(data, datalen, &written))
+ {
+ case FLVPROCESSOR_WRITE_ERROR:
+ return 1;
+ case FLVPROCESSOR_WRITE_WAIT:
+ needSleep=true;
+ break;
+ }
+
+ datalen -= written;
+
+ if (written)
+ {
+ while (1)
+ {
+ if (killswitch)
+ return 1;
+
+ int ret = processor->Process();
+ if (ret == FLV_OK)
+ continue;
+ if (ret == FLV_NEED_MORE_DATA)
+ break;
+ if (ret == FLV_ERROR)
+ return 1;
+ }
+ }
+ }
+ return !!killswitch;
+}
+
+bool FLVReader::GetFrame(size_t frameIndex, FrameData &frameData)
+{
+ if (processor)
+ return processor->GetFrame(frameIndex, frameData);
+ else
+ return false;
+}
+
+/*
+Test URLs (valid of as oct 23 2007)
+http://pdl.stream.aol.com/aol/us/aolmusic/artists/astralwerks/2220s/2220s_shootyourgun_hakjfh_700_dl.flv
+http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_fallbackintomylife_700_dl.flv
+http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_gonesoyoung_700_dl.flv
+http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_soyesterday_700_dl.flv
+http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_watchingoverme_700_dl.flv
+
+a potential crasher:
+http://pdl.stream.aol.com/aol/us/aolcomvideo/TVGuide/johncmcginley_6346/johncmcginley_6346_460_700_dl.flv
+
+FLV streams:
+http://208.80.52.96/CBS_R20_452P_F128?ext=.flv
+http://208.80.54.37/KROQFM?ext=.flv
+http://208.80.52.96/CBS_R20_568P_F128?ext=.flv
+*/
+DWORD FLVReader::ParserThread()
+{
+ if (PathIsURL(url))
+ {
+ Downloader downloader;
+ downloader.Download(AutoChar(url), this);
+ }
+ else
+ {
+ processor = new FileProcessor(url);
+ ProcessFile();
+ }
+ end_of_stream=true;
+ return 0;
+}
+
+void FLVReader::Kill()
+{
+ killswitch=true;
+ WaitForSingleObject(flvThread, INFINITE);
+ CloseHandle(flvThread);
+}
+
+void FLVReader::SignalKill()
+{
+ killswitch=true;
+}
+
+bool FLVReader::IsEOF()
+{
+ return end_of_stream;
+}
+
+bool FLVReader::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
+{
+ if (processor)
+ return processor->GetPosition(time_in_ms, frameIndex, needVideoKeyFrame);
+ else
+ return false;
+}
+
+uint32_t FLVReader::GetMaxTimestamp()
+{
+ if (processor)
+ return processor->GetMaxTimestamp();
+ else
+ return 0;
+}
+
+bool FLVReader::IsStreaming()
+{
+ if (processor)
+ return processor->IsStreaming();
+ else
+ return true;
+}
+
+FLVHeader *FLVReader::GetHeader()
+{
+ if (processor)
+ return processor->GetHeader();
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVReader.h b/Src/Plugins/Input/in_flv/FLVReader.h
new file mode 100644
index 00000000..27e68183
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVReader.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_IN_FLV_FLVREADER_H
+#define NULLSOFT_IN_FLV_FLVREADER_H
+
+#include <windows.h>
+#include <bfc/platform/types.h>
+#include "FLVStreamheader.h"
+#include "../nu/AutoLock.h"
+#include "BackgroundDownloader.h"
+#include "FLVProcessor.h"
+
+class FLVReader : private Downloader::DownloadCallback
+{
+public:
+ FLVReader(const wchar_t *_url);
+ ~FLVReader();
+
+ bool GetFrame(size_t frameIndex, FrameData &frameData);
+ bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
+ uint64_t Seek(uint64_t position);
+ size_t Read(void *data, size_t bytes);
+ void Kill();
+ void SignalKill();
+ bool IsEOF();
+ uint32_t GetMaxTimestamp();
+ uint64_t GetProcessedPosition();
+ bool IsStreaming();
+ FLVHeader *GetHeader();
+private:
+ void ProcessFile();
+ int OnConnect(api_httpreceiver *http);
+ int OnData(void *data, size_t datalen);
+ int Process();
+ DWORD CALLBACK ParserThread();
+ static DWORD CALLBACK ParserThreadStub(LPVOID param) { return ((FLVReader *)param)->ParserThread(); }
+
+private:
+ bool killswitch;
+
+ HANDLE flvThread;
+ bool end_of_stream;
+ wchar_t *url;
+
+ /* because we won't know until after opening the stream whether it's progressive download
+ or a real-time stream, we need have a pointer to a virtual base class to do the processing
+ we'll create a different one depending on what kind of stream */
+ FLVProcessor *processor;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp
new file mode 100644
index 00000000..8f1ef49e
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp
@@ -0,0 +1,36 @@
+#include "FLVStreamHeader.h"
+#include "FLVUtil.h"
+/*
+(c) 2006 Nullsoft, Inc.
+Author: Ben Allison benski@nullsoft.com
+*/
+
+/*
+PreviousTagSize - uint32 - total size of previous tag, presumably for stream continuity checking. big endian
+Type - uint8 - what does this frame contain? 0x12=meta, 0x8=audio, 0x9=video
+BodyLength - uint24 - size of the data following this header. big endian
+Timestamp - uint24 - timestamp (milliseconds). big endian
+Timestamp High - uint8 - high 8 bits of timestamp (to form 32bit timestamp)
+Stream ID - uint24 - always zero
+*/
+
+bool FLVStreamHeader::Read(uint8_t *data, size_t size)
+{
+ if (size < 15)
+ return false; // header size too small
+
+ previousSize = FLV::Read32(&data[0]);
+ type = data[4];
+ dataSize = FLV::Read24(&data[5]);
+ timestamp = FLV::Read24(&data[8]);
+ uint8_t timestampHigh = FLV::Read8(&data[11]);
+ timestamp |= (timestampHigh << 24);
+ streamID = FLV::Read24(&data[12]);
+
+ if (streamID != 0)
+ return false;
+
+ return true;
+
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.h b/Src/Plugins/Input/in_flv/FLVStreamHeader.h
new file mode 100644
index 00000000..855d4159
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_FLVSTREAMHEADER_H
+#define NULLSOFT_FLVSTREAMHEADER_H
+
+#include <bfc/platform/types.h>
+namespace FLV
+{
+ enum
+ {
+ FRAME_TYPE_AUDIO = 0x8,
+ FRAME_TYPE_VIDEO = 0x9,
+ FRAME_TYPE_METADATA = 0x12,
+ };
+}
+class FLVStreamHeader
+{
+public:
+ bool Read(unsigned __int8 *data, size_t size); // size must be >=15, returns "true" if this was a valid header
+
+ // attributes, consider these read-only
+ uint32_t previousSize;
+ uint8_t type;
+ uint32_t dataSize;
+ uint32_t timestamp;
+ uint32_t streamID;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVUtil.h b/Src/Plugins/Input/in_flv/FLVUtil.h
new file mode 100644
index 00000000..9b9da776
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVUtil.h
@@ -0,0 +1,90 @@
+#ifndef NULLSOFT_FLVUTIL_H
+#define NULLSOFT_FLVUTIL_H
+
+#include <memory.h>
+#include <bfc/platform/types.h>
+namespace FLV
+{
+ // reads 32 bits from data and converts from big endian
+inline uint32_t Read32(uint8_t *data)
+{
+ uint32_t returnVal;
+#ifdef __BIG_ENDIAN__
+ returnVal = *(uint32_t *)(&data[0]);
+#else
+ // need to swap endianness
+ uint8_t *swap = (uint8_t *)&returnVal;
+ swap[0]=data[3];
+ swap[1]=data[2];
+ swap[2]=data[1];
+ swap[3]=data[0];
+#endif
+ return returnVal;
+
+}
+// reads 24 bits from data, converts from big endian, and returns as a 32bit int
+inline uint32_t Read24(uint8_t *data)
+{
+ uint32_t returnVal=0;
+ uint8_t *swap = (uint8_t *)&returnVal;
+#ifdef __BIG_ENDIAN__
+ swap[1]=data[0];
+ swap[2]=data[1];
+ swap[3]=data[2];
+ returnVal = *(uint32_t *)(&data[0]);
+#else
+ // need to swap endianness
+ swap[0]=data[2];
+ swap[1]=data[1];
+ swap[2]=data[0];
+#endif
+ return returnVal;
+
+}
+
+// reads 16 bits from data and converts from big endian
+inline uint16_t Read16(uint8_t *data)
+{
+ uint16_t returnVal;
+#ifdef __BIG_ENDIAN__
+ returnVal = *(uint16_t *)(&data[0]);
+#else
+ // need to swap endianness
+ uint8_t *swap = (uint8_t *)&returnVal;
+ swap[0]=data[1];
+ swap[1]=data[0];
+#endif
+ return returnVal;
+}
+
+// reads 16 bits from data and converts from big endian
+inline uint8_t Read8(uint8_t *data)
+{
+ uint8_t returnVal;
+
+ returnVal = *(uint8_t *)(&data[0]);
+ return returnVal;
+}
+
+// reads a double from data
+inline double ReadDouble(uint8_t *data)
+{
+ double returnVal;
+#ifdef __BIG_ENDIAN__
+ memcpy(&returnVal, data, 8);
+ #else
+ uint8_t *swap = (uint8_t *)&returnVal;
+ swap[0]=data[7];
+ swap[1]=data[6];
+ swap[2]=data[5];
+ swap[3]=data[4];
+ swap[4]=data[3];
+ swap[5]=data[2];
+ swap[6]=data[1];
+ swap[7]=data[0];
+ #endif
+ return returnVal;
+}
+
+}
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp
new file mode 100644
index 00000000..44c2cf69
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp
@@ -0,0 +1,24 @@
+#include "FLVVideoHeader.h"
+
+/*
+(c) 2006 Nullsoft, Inc.
+Author: Ben Allison benski@nullsoft.com
+*/
+
+/*
+codecID - 4 bits - 2: Sorensen H.263, 3: Screen video, 4: On2 VP6
+frameType - 4 bits - 1: keyframe, 2: inter frame, 3: disposable inter frame
+*/
+
+bool FLVVideoHeader::Read(unsigned __int8 *data, size_t size)
+{
+ if (size < 1)
+ return false; // header size too small
+
+ unsigned __int8 byte = data[0];
+
+ format = (byte & 0x0f) >> 0;
+ frameType = (byte & 0xf0) >> 4;
+
+ return true;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.h b/Src/Plugins/Input/in_flv/FLVVideoHeader.h
new file mode 100644
index 00000000..6a40d886
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.h
@@ -0,0 +1,32 @@
+#ifndef NULLSOFT_FLVVIDEOHEADER_H
+#define NULLSOFT_FLVVIDEOHEADER_H
+
+namespace FLV
+{
+ const int VIDEO_FORMAT_JPEG = 1;
+ const int VIDEO_FORMAT_SORENSON = 2; // H.263
+ const int VIDEO_FORMAT_SCREEN = 3;
+ const int VIDEO_FORMAT_VP6 = 4;
+ const int VIDEO_FORMAT_VP62 = 5;
+ const int VIDEO_FORMAT_SCREEN_V2 = 6;
+ const int VIDEO_FORMAT_AVC = 7; // MPEG-4 Part 10
+
+ const int VIDEO_FRAMETYPE_KEYFRAME = 1;
+ const int VIDEO_FRAMETYPE_IFRAME = 2;
+ const int VIDEO_FRAMETYPE_IFRAME_DISPOSABLE = 3;
+ const int VIDEO_FRAMETYPE_GENERATED = 4;
+ const int VIDEO_FRAMETYPE_INFO = 5;
+};
+
+
+class FLVVideoHeader
+{
+public:
+ bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header
+
+ // attributes, consider these read-only
+ int format;
+ int frameType;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FileProcessor.cpp b/Src/Plugins/Input/in_flv/FileProcessor.cpp
new file mode 100644
index 00000000..f2351ee0
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FileProcessor.cpp
@@ -0,0 +1,201 @@
+#include "FileProcessor.h"
+#include "FLVHeader.h"
+#include "FLVVideoHeader.h"
+
+static int64_t Seek64(HANDLE hf, int64_t distance, DWORD MoveMethod)
+{
+ LARGE_INTEGER li;
+ li.QuadPart = distance;
+ li.LowPart = SetFilePointer(hf, li.LowPart, &li.HighPart, MoveMethod);
+ if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
+ {
+ li.QuadPart = -1;
+ }
+
+ return li.QuadPart;
+}
+
+int64_t FileSize64(HANDLE file)
+{
+ LARGE_INTEGER position;
+ position.QuadPart=0;
+ position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
+
+ if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
+ return INVALID_FILE_SIZE;
+ else
+ return position.QuadPart;
+}
+
+FileProcessor::FileProcessor()
+{
+ Init();
+}
+
+FileProcessor::FileProcessor(const wchar_t *filename)
+{
+ Init();
+ processedCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+ readCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+ writePosition=FileSize64(readCursor);
+}
+
+FileProcessor::~FileProcessor()
+{
+ if (processedCursor != INVALID_HANDLE_VALUE)
+ CloseHandle(processedCursor);
+ if (readCursor != INVALID_HANDLE_VALUE)
+ CloseHandle(readCursor);
+}
+
+void FileProcessor::Init()
+{
+ processedPosition=0;
+ processedCursor=INVALID_HANDLE_VALUE;
+ writePosition=0;
+ readCursor=INVALID_HANDLE_VALUE;
+ flen=0;
+ maxTimeStamp=0;
+ headerOK=false;
+}
+
+int FileProcessor::Process()
+{
+ int64_t oldPosition = Seek64(processedCursor, 0, FILE_CURRENT);
+
+ // read file header if we're at the beginning
+ if (processedPosition == 0)
+ {
+ if (writePosition-processedPosition >= 9) // need at least nine bytes for the FLV file header
+ {
+ uint8_t data[9] = {0};
+ DWORD bytesRead=0;
+ ReadFile(processedCursor, data, 9, &bytesRead, NULL);
+ if (header.Read(data, bytesRead))
+ {
+ headerOK=true;
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ processedPosition += bytesRead;
+ return FLV_OK; // we'll make our logic just a little bit more sane by only processing one frame per pass.
+ }
+ else
+ return FLV_ERROR;
+ }
+
+ Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
+ return FLV_NEED_MORE_DATA;
+ }
+
+ if (writePosition-processedPosition >= 15) // need at least fifteen bytes for the FLV frame header
+ {
+ uint8_t data[15] = {0};
+ DWORD bytesRead=0;
+ ReadFile(processedCursor, data, 15, &bytesRead, NULL);
+ FrameData frameData;
+ if (frameData.header.Read(data, bytesRead))
+ {
+ if (frameData.header.dataSize + processedPosition + 15 <= writePosition)
+ {
+ frameData.keyFrame = false;
+ if (frameData.header.type == FLV::FRAME_TYPE_VIDEO)
+ {
+ FLVVideoHeader videoHeader;
+ DWORD videoHeaderRead=0;
+ ReadFile(processedCursor, data, 1, &videoHeaderRead, NULL);
+ if (videoHeader.Read(data, bytesRead))
+ {
+ if (videoHeader.frameType == FLV::VIDEO_FRAMETYPE_KEYFRAME)
+ {
+ frameData.keyFrame = true;
+ }
+ }
+ Seek64(processedCursor, -(int64_t)videoHeaderRead, FILE_CURRENT);
+ // roll back file cursor from ReadFile
+ }
+
+ { // critsec enter
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ // record the offset where we found it
+ frameData.location = processedPosition;
+ maxTimeStamp=max(maxTimeStamp, frameData.header.timestamp);
+ frames.push_back(frameData);
+ } // critsec leave
+ Seek64(processedCursor, frameData.header.dataSize, FILE_CURRENT);
+
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ processedPosition+=bytesRead+frameData.header.dataSize;
+ return FLV_OK;
+ }
+ Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
+ return FLV_NEED_MORE_DATA;
+ }
+ else
+ {
+ return FLV_ERROR;
+ }
+ }
+ Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
+ return FLV_NEED_MORE_DATA;
+}
+
+uint32_t FileProcessor::GetMaxTimestamp()
+{
+ return maxTimeStamp;
+}
+
+bool FileProcessor::GetFrame(size_t frameIndex, FrameData &frameData)
+{
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ if (frameIndex < frames.size())
+ {
+ frameData = frames[frameIndex];
+ return true;
+ }
+ return false;
+}
+
+uint64_t FileProcessor::GetProcessedPosition()
+{
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ return processedPosition;
+}
+
+size_t FileProcessor::Read(void *data, size_t bytes)
+{
+ DWORD bytesRead = 0;
+ ReadFile(readCursor, data, (DWORD)bytes, &bytesRead, NULL);
+ return bytesRead;
+}
+
+uint64_t FileProcessor::Seek(uint64_t position)
+{
+ return Seek64(readCursor, position, FILE_BEGIN);
+}
+
+bool FileProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
+{
+ Nullsoft::Utility::AutoLock lock(frameGuard);
+ // TODO: binary search
+ for (size_t f=0;f!=frames.size();f++)
+ {
+ if (frames[f].header.timestamp >= (uint32_t)time_in_ms)
+ {
+ if (needVideoKeyFrame)
+ {
+ while (f != 0 && !frames[f].keyFrame)
+ f--;
+ }
+ *frameIndex = f;
+ return true;
+ }
+ }
+ return false;
+}
+
+FLVHeader *FileProcessor::GetHeader()
+{
+ if (headerOK)
+ return &header;
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/FileProcessor.h b/Src/Plugins/Input/in_flv/FileProcessor.h
new file mode 100644
index 00000000..d0fb6bd3
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/FileProcessor.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "FLVProcessor.h"
+#include <windows.h>
+#include <vector>
+#include "../nu/AutoLock.h"
+
+class FileProcessor : public FLVProcessor
+{
+public:
+ FileProcessor(const wchar_t *filename);
+ ~FileProcessor();
+private:
+ /* FLVProcessor virtual method overrides */
+ int Write(void *data, size_t datalen, size_t *written) { return 1; }
+ uint64_t GetProcessedPosition();
+ uint32_t GetMaxTimestamp();
+ bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
+ bool IsStreaming() { return false; }
+ FLVHeader *GetHeader();
+public:
+ /* FLVProcessor virtual method overrides, continued
+ (these are public so FileProcessor can be used standalone */
+ int Process();
+ uint64_t Seek(uint64_t position);
+ size_t Read(void *data, size_t bytes);
+ bool GetFrame(size_t frameIndex, FrameData &frameData);
+
+protected:
+ FileProcessor();
+ uint64_t processedPosition;
+ uint64_t writePosition;
+ HANDLE processedCursor, readCursor;
+ // file positions of samples
+ std::vector<FrameData> frames;
+ Nullsoft::Utility::LockGuard frameGuard;
+ uint64_t flen;
+
+private:
+ void Init();
+ uint32_t maxTimeStamp;
+ FLVHeader header;
+ bool headerOK;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/PlayThread.cpp b/Src/Plugins/Input/in_flv/PlayThread.cpp
new file mode 100644
index 00000000..33fa735e
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/PlayThread.cpp
@@ -0,0 +1,703 @@
+#include "main.h"
+#include <windows.h>
+#include "api__in_flv.h"
+#include "../Winamp/wa_ipc.h"
+#include "FLVHeader.h"
+#include "FLVStreamHeader.h"
+#include "FLVAudioHeader.h"
+#include "FLVVideoHeader.h"
+#include "FLVMetadata.h"
+#include <malloc.h>
+#include <stdio.h>
+#include <shlwapi.h>
+#include "VideoThread.h"
+#include "FLVReader.h"
+#include "resource.h"
+#include "FLVCOM.h"
+#include <api/service/waservicefactory.h>
+#include "ifc_flvaudiodecoder.h"
+#include "svc_flvdecoder.h"
+#include "../nu/AudioOutput.h"
+
+#define PRE_BUFFER_MS 5000.0
+#define PRE_BUFFER_MAX (1024*1024)
+
+uint32_t last_timestamp=0;
+static bool audioOpened;
+int bufferCount;
+static int bits, channels, sampleRate;
+static double dataRate_audio, dataRate_video;
+static double dataRate;
+static uint64_t prebuffer;
+bool mute=false;
+uint32_t first_timestamp;
+static size_t audio_buffered=0;
+void VideoStop();
+static ifc_flvaudiodecoder *audioDecoder=0;
+static bool checked_in_swf=false;
+extern bool video_only;
+
+class FLVWait
+{
+public:
+ int WaitOrAbort(int len)
+ {
+ if (WaitForSingleObject(killswitch, len) == WAIT_OBJECT_0)
+ return 1;
+
+ return 0;
+ }
+};
+
+static nu::AudioOutput<FLVWait> outputter(&plugin);
+
+static void Buffering(int bufStatus, const wchar_t *displayString)
+{
+ if (bufStatus < 0 || bufStatus > 100)
+ return;
+
+ char tempdata[75*2] = {0, };
+
+ int csa = plugin.SAGetMode();
+ if (csa & 1)
+ {
+ for (int x = 0; x < bufStatus*75 / 100; x ++)
+ tempdata[x] = x * 16 / 75;
+ }
+ else if (csa&2)
+ {
+ int offs = (csa & 1) ? 75 : 0;
+ int x = 0;
+ while (x < bufStatus*75 / 100)
+ {
+ tempdata[offs + x++] = -6 + x * 14 / 75;
+ }
+ while (x < 75)
+ {
+ tempdata[offs + x++] = 0;
+ }
+ }
+ else if (csa == 4)
+ {
+ tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
+ }
+ if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
+
+ /*
+ TODO
+ wchar_t temp[64] = {0};
+ StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
+ SetStatus(temp);
+ */
+ //SetVideoStatusText(temp); // TODO: find a way to set the old status back
+ videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
+}
+
+static bool Audio_IsSupported(int type)
+{
+ size_t n = 0;
+ waServiceFactory *factory = NULL;
+ while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
+ {
+ svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
+ if (creator)
+ {
+ int supported = creator->HandlesAudio(type);
+ factory->releaseInterface(creator);
+ if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
+ return true;
+ }
+ }
+ return false;
+}
+
+static ifc_flvaudiodecoder *CreateAudioDecoder(const FLVAudioHeader &header)
+{
+ ifc_flvaudiodecoder *audio_decoder=0;
+ size_t n=0;
+ waServiceFactory *factory = NULL;
+ while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
+ {
+ svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
+ if (creator)
+ {
+ if (creator->CreateAudioDecoder(header.stereo, header.bits, header.sampleRate, header.format, &audio_decoder) == FLV_AUDIO_SUCCESS)
+ return audio_decoder;
+
+ factory->releaseInterface(creator);
+ }
+ }
+ return 0;
+}
+
+void OnStart()
+{
+ Video_Init();
+
+ audioOpened = false;
+ audioDecoder = 0;
+
+ bufferCount=0;
+ mute = false;
+ audio_buffered=0;
+
+ if (!videoOutput)
+ videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
+}
+
+void Audio_Close()
+{
+ if (audioDecoder)
+ audioDecoder->Close();
+ audioDecoder=0;
+}
+
+static bool OpenAudio(unsigned int sampleRate, unsigned int channels, unsigned int bits)
+{
+ if (!outputter.Open(0, channels, sampleRate, bits, -666))
+ return false;
+ audioOpened = true;
+ return true;
+}
+
+static void DoBuffer(FLVReader &reader)
+{
+ // TODO: we should pre-buffer after getting the first audio + video frames
+ // so we can estimate bitrate
+ for (;;)
+ {
+ uint64_t processedPosition = reader.GetProcessedPosition();
+ if (processedPosition < prebuffer && !reader.IsEOF())
+ {
+ Buffering((int)((100ULL * processedPosition) / prebuffer), WASABI_API_LNGSTRINGW(IDS_BUFFERING));
+ if (WaitForSingleObject(killswitch, 100) == WAIT_OBJECT_0)
+ break;
+ else
+ continue;
+ }
+ else
+ break;
+ }
+}
+
+
+char pcmdata[65536] = {0};
+size_t outlen = 32768;
+
+static uint32_t audio_type;
+static void OnAudio(FLVReader &reader, void *data, size_t length, const FLVAudioHeader &header)
+{
+ if (!audioDecoder)
+ {
+ audioDecoder = CreateAudioDecoder(header);
+ audio_type = header.format;
+ video_only=false;
+ }
+
+ if (audioDecoder)
+ {
+
+ if (!audioDecoder->Ready())
+ {
+ //first_timestamp = -1;
+ }
+
+ outlen = sizeof(pcmdata)/2 - audio_buffered;
+ double bitrate = 0;
+ int ret = audioDecoder->DecodeSample(data, length, pcmdata+audio_buffered, &outlen, &bitrate);
+ if (ret == FLV_AUDIO_SUCCESS)
+ {
+ outlen+=audio_buffered;
+ audio_buffered=0;
+ if (bitrate && dataRate_audio != bitrate)
+ {
+ dataRate_audio = bitrate;
+ dataRate = dataRate_audio + dataRate_video;
+ plugin.SetInfo((int)dataRate, -1, -1, 1);
+ }
+
+ if (!audioOpened)
+ {
+ // pre-populate values for decoders that use the header info (e.g. ADPCM)
+ sampleRate=header.sampleRate;
+ channels = header.stereo?2:1;
+ bits = header.bits;
+
+ if (audioDecoder->GetOutputFormat((unsigned int *)&sampleRate, (unsigned int *)&channels, (unsigned int *)&bits) == FLV_AUDIO_SUCCESS)
+ {
+
+ // buffer (if needed)
+ prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
+ if (prebuffer > PRE_BUFFER_MAX)
+ prebuffer=PRE_BUFFER_MAX;
+ DoBuffer(reader); // benski> admittedly a crappy place to call this
+ if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0)
+ return;
+
+ OpenAudio(sampleRate, channels, bits);
+ }
+ }
+
+ if (mute)
+ {
+ if (bits == 8) // 8 bit is signed so 128 is zero voltage
+ memset(pcmdata, 0x80, outlen);
+ else
+ memset(pcmdata, 0, outlen);
+ }
+
+ if (audioOpened && outlen)
+ {
+ outputter.Write(pcmdata, outlen);
+ }
+ else
+ {
+ audio_buffered=outlen;
+ }
+
+ }
+ else if (ret == FLV_AUDIO_NEEDS_MORE_INPUT)
+ {
+ plugin.SetInfo(-1, -1, -1, 0);
+ }
+
+ }
+}
+
+
+#define PREBUFFER_BYTES 2048ULL
+
+
+enum
+{
+ CODEC_CHECK_NONE=0,
+ CODEC_CHECK_AUDIO=1,
+ CODEC_CHECK_VIDEO=2,
+ CODEC_CHECK_UNSURE = -1,
+};
+
+static bool CheckSWF()
+{
+ if (!checked_in_swf)
+ {
+ const wchar_t *pluginsDir = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
+ wchar_t in_swf_path[MAX_PATH] = {0};
+ PathCombineW(in_swf_path, pluginsDir, L"in_swf.dll");
+ in_swf = LoadLibraryW(in_swf_path);
+ checked_in_swf = true;
+ }
+ return !!in_swf;
+}
+
+static void CALLBACK SWFAPC(ULONG_PTR param)
+{
+ if (in_swf)
+ {
+ typedef In_Module *(*MODULEGETTER)();
+ MODULEGETTER moduleGetter=0;
+ moduleGetter = (MODULEGETTER)GetProcAddress(in_swf, "winampGetInModule2");
+ if (moduleGetter)
+ swf_mod = moduleGetter();
+ }
+
+ if (swf_mod)
+ {
+ if (swf_mod->Play(playFile))
+ swf_mod=0;
+ }
+
+ if (!swf_mod)
+ {
+ if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+ }
+}
+
+static bool Audio_DecoderReady()
+{
+ if (!audioDecoder)
+ return true;
+
+ return !!audioDecoder->Ready();
+}
+
+static bool DecodersReady()
+{
+ return Audio_DecoderReady() && Video_DecoderReady();
+}
+
+DWORD CALLBACK PlayProcedure(LPVOID param)
+{
+ int needCodecCheck=CODEC_CHECK_UNSURE;
+ int missingCodecs=CODEC_CHECK_NONE;
+ size_t codecCheckFrame=0;
+
+ dataRate_audio=0;
+ dataRate_video=0;
+ dataRate=0;
+ first_timestamp=-1;
+
+ outputter.Init(plugin.outMod);
+
+ FLVReader reader(playFile);
+ size_t i=0;
+ bool hasDuration=false;
+
+ OnStart();
+ plugin.is_seekable=0;
+
+ prebuffer = PREBUFFER_BYTES;
+ bool first_frame_parsed=false;
+
+ for (;;)
+ {
+ DoBuffer(reader);
+
+ if (WaitForSingleObject(killswitch, paused?100:0) == WAIT_OBJECT_0)
+ break;
+
+ if (paused)
+ continue;
+
+ if (m_need_seek != -1 && DecodersReady() && first_frame_parsed)
+ {
+ if (reader.GetPosition(m_need_seek, &i, video_opened))
+ {
+ VideoFlush();
+ if (audioDecoder)
+ audioDecoder->Flush();
+ FrameData frameData;
+ reader.GetFrame(i, frameData);
+ outputter.Flush(frameData.header.timestamp);
+
+ if (video_only)
+ {
+ video_clock.Seek(frameData.header.timestamp);
+ }
+ }
+ uint32_t first_timestamp = 0;
+ m_need_seek=-1;
+ }
+
+ // update the movie length
+ if (!hasDuration)
+ {
+ if (reader.IsStreaming())
+ {
+ hasDuration=true;
+ g_length = -1000;
+ plugin.is_seekable=0;
+ }
+ else
+ {
+ g_length=reader.GetMaxTimestamp();
+ if (g_length != -1000)
+ plugin.is_seekable=1;
+ }
+ PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
+ }
+
+ /** if it's a local file or an HTTP asset with content-length
+ ** we verify that the FLV has codecs we can decode
+ ** depending on settings, we will do one of the following with unsupport assets
+ ** 1) Play anyway (e.g. an H.264 video might play audio only)
+ ** 2) Use in_swf to play back
+ ** 3) skip
+ **/
+
+ if (needCodecCheck == CODEC_CHECK_UNSURE)
+ {
+ FLVHeader *header = reader.GetHeader();
+ if (header)
+ {
+ needCodecCheck=CODEC_CHECK_NONE;
+ if (!reader.IsStreaming())
+ {
+ if (header->hasVideo)
+ needCodecCheck |= CODEC_CHECK_VIDEO;
+ if (header->hasAudio)
+ needCodecCheck |= CODEC_CHECK_AUDIO;
+
+ }
+ if (!header->hasAudio)
+ {
+ video_only=true;
+ video_clock.Start();
+ }
+ }
+ }
+
+ if (needCodecCheck)
+ {
+ FrameData frameData;
+ if (reader.GetFrame(codecCheckFrame, frameData))
+ {
+ FLVStreamHeader &frameHeader = frameData.header;
+ if ((needCodecCheck & CODEC_CHECK_AUDIO) && frameHeader.type == FLV::FRAME_TYPE_AUDIO)
+ {
+ reader.Seek(frameData.location+15); // TODO: check for -1 return value
+
+ uint8_t data[1] = {0};
+ FLVAudioHeader audioHeader;
+ size_t bytesRead = reader.Read(data, 1);
+ if (audioHeader.Read(data, bytesRead))
+ {
+ if (Audio_IsSupported(audioHeader.format))
+ {
+ needCodecCheck &= ~CODEC_CHECK_AUDIO;
+ }
+ else
+ {
+ needCodecCheck &= ~CODEC_CHECK_AUDIO;
+ missingCodecs|=CODEC_CHECK_AUDIO;
+ video_only=true;
+ }
+ }
+ }
+ if ((needCodecCheck & CODEC_CHECK_VIDEO) && frameHeader.type == FLV::FRAME_TYPE_VIDEO)
+ {
+ reader.Seek(frameData.location+15); // TODO: check for -1 return value
+
+ uint8_t data[1] = {0};
+ FLVVideoHeader videoHeader;
+ size_t bytesRead = reader.Read(data, 1);
+ if (videoHeader.Read(data, bytesRead))
+ {
+ if (Video_IsSupported(videoHeader.format))
+ {
+ needCodecCheck &= ~CODEC_CHECK_VIDEO;
+ }
+ else
+ {
+ needCodecCheck &= ~CODEC_CHECK_VIDEO;
+ missingCodecs|=CODEC_CHECK_VIDEO;
+ }
+ }
+ }
+
+ codecCheckFrame++;
+ }
+ else if (reader.IsEOF())
+ break;
+ else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
+ break;
+
+ }
+
+ if (needCodecCheck)
+ continue; // don't start decoding until we've done our codec check
+
+ if (missingCodecs)
+ {
+ // use in_swf to play this one
+ if (CheckSWF())
+ {
+ HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle();
+ if (mainThread)
+ {
+ reader.Kill();
+ Audio_Close();
+ Video_Stop();
+ Video_Close();
+ QueueUserAPC(SWFAPC, mainThread,0);
+ CloseHandle(mainThread);
+ return 0;
+ }
+ }
+ else
+ {
+ FLVHeader *header = reader.GetHeader();
+ if (header)
+ {
+ bool can_play_something = false;
+
+ if (header->hasVideo && !(missingCodecs & CODEC_CHECK_VIDEO))
+ can_play_something = true; // we can play video
+ else if (header->hasAudio && !(missingCodecs & CODEC_CHECK_AUDIO))
+ can_play_something = true; // we can play audio
+
+ if (can_play_something)
+ {
+ missingCodecs=false;
+ continue;
+ }
+ }
+ break; // no header or no codecs at all, bail out
+ }
+ }
+
+ /* --- End Codec Check --- */
+ FrameData frameData;
+ if (reader.GetFrame(i, frameData))
+ {
+ i++;
+ uint8_t data[2] = {0};
+ FLVStreamHeader &frameHeader = frameData.header;
+ reader.Seek(frameData.location+15); // TODO: check for -1 return value
+ switch (frameHeader.type)
+ {
+ default:
+#ifdef _DEBUG
+ DebugBreak();
+#endif
+ break;
+ case FLV::FRAME_TYPE_AUDIO: // audio
+ first_frame_parsed=true;
+ if (m_need_seek == -1 || !Audio_DecoderReady())
+ {
+ FLVAudioHeader audioHeader;
+ size_t bytesRead = reader.Read(data, 1);
+ if (audioHeader.Read(data, bytesRead))
+ {
+ size_t dataSize = frameHeader.dataSize - 1;
+
+ uint8_t *audiodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
+ if (audiodata)
+ {
+ bytesRead = reader.Read(audiodata, dataSize);
+ if (bytesRead != dataSize)
+ break;
+ if (!reader.IsStreaming())
+ {
+ if (first_timestamp == -1)
+ first_timestamp = frameHeader.timestamp;
+ last_timestamp = frameHeader.timestamp;
+ last_timestamp = plugin.outMod->GetWrittenTime();
+ }
+
+ OnAudio(reader, audiodata, dataSize, audioHeader);
+ free(audiodata);
+ }
+ }
+ }
+ break;
+ case FLV::FRAME_TYPE_VIDEO: // video
+ first_frame_parsed=true;
+ if (m_need_seek == -1 || !Video_DecoderReady())
+ {
+
+ FLVVideoHeader videoHeader;
+ size_t bytesRead = reader.Read(data, 1);
+ if (videoHeader.Read(data, bytesRead))
+ {
+ size_t dataSize = frameHeader.dataSize - 1;
+
+ uint8_t *videodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
+ if (videodata)
+ {
+ bytesRead = reader.Read(videodata, dataSize);
+ if (bytesRead != dataSize)
+ {
+ free(videodata);
+ break;
+ }
+ if (!OnVideo(videodata, dataSize, videoHeader.format, frameHeader.timestamp))
+ free(videodata);
+ }
+ }
+ }
+ break;
+ case FLV::FRAME_TYPE_METADATA: // metadata
+ {
+ first_frame_parsed=true;
+ size_t dataSize = frameHeader.dataSize;
+ uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
+ if (metadatadata)
+ {
+ size_t bytesRead = reader.Read(metadatadata, dataSize);
+ if (bytesRead != dataSize)
+ {
+ free(metadatadata);
+ break;
+ }
+ FLVMetadata metadata;
+ metadata.Read(metadatadata, dataSize);
+ for ( FLVMetadata::Tag *tag : metadata.tags )
+ {
+ if (!_wcsicmp(tag->name.str, L"onMetaData"))
+ {
+ AMFType *amf_stream_title;
+
+ amf_stream_title = tag->parameters->array[L"streamTitle"];
+ if (amf_stream_title && amf_stream_title->type == AMFType::TYPE_STRING)
+ {
+ AMFString *stream_title_string = (AMFString *)amf_stream_title;
+ Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
+ free(stream_title);
+ stream_title = _wcsdup(stream_title_string->str);
+ PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
+ }
+
+ AMFType *w, *h;
+ w = tag->parameters->array[L"width"];
+ h = tag->parameters->array[L"height"];
+ if (w && h)
+ {
+ width = (int)AMFGetDouble(w);
+ height = (int)AMFGetDouble(h);
+ }
+
+ AMFType *duration;
+ duration=tag->parameters->array[L"duration"];
+ if (duration)
+ {
+ hasDuration=true;
+ plugin.is_seekable=1;
+ g_length = (int)(AMFGetDouble(duration)*1000.0);
+ }
+
+ // grab the data rate. we'll need this to determine a good pre-buffer.
+ AMFType *videoDataRate, *audioDataRate;
+ videoDataRate=tag->parameters->array[L"videodatarate"];
+ audioDataRate=tag->parameters->array[L"audiodatarate"];
+ if (videoDataRate || audioDataRate)
+ {
+ dataRate_audio = audioDataRate?AMFGetDouble(audioDataRate):0.0;
+ dataRate_video = videoDataRate?AMFGetDouble(videoDataRate):0.0;
+
+ dataRate = dataRate_audio + dataRate_video;
+
+ if (dataRate < 1.0f)
+ dataRate = 720.0f;
+ plugin.SetInfo((int)dataRate, -1, -1, 1);
+ prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
+ if (prebuffer > PRE_BUFFER_MAX)
+ prebuffer=PRE_BUFFER_MAX;
+
+ PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
+ }
+ }
+ flvCOM.MetadataCallback(tag);
+ }
+ free(metadatadata);
+ }
+ }
+ break;
+ }
+ }
+ else if (reader.IsEOF())
+ break;
+ else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
+ break;
+ }
+
+ reader.SignalKill();
+
+ if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
+ {
+ outputter.Write(0,0);
+ outputter.WaitWhilePlaying();
+
+ if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+ }
+
+ SetEvent(killswitch);
+
+ Video_Stop();
+ Video_Close();
+
+ reader.Kill();
+ Audio_Close();
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp
new file mode 100644
index 00000000..c4e3c5d7
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp
@@ -0,0 +1,34 @@
+#include "ProgressiveProcessor.h"
+
+ProgressiveProcessor::ProgressiveProcessor()
+{
+ tempFile[0]=0;
+ writeCursor=INVALID_HANDLE_VALUE;
+
+ wchar_t tempPath[MAX_PATH-14] = {0};
+ GetTempPath(MAX_PATH-14, tempPath);
+ GetTempFileName(tempPath, L"wfv", 0, tempFile);
+
+ writeCursor=CreateFile(tempFile, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
+ processedCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+ readCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+}
+
+ProgressiveProcessor::~ProgressiveProcessor()
+{
+ if (writeCursor != INVALID_HANDLE_VALUE)
+ CloseHandle(writeCursor);
+
+ if (tempFile[0])
+ DeleteFile(tempFile);
+}
+
+int ProgressiveProcessor::Write(void *data, size_t datalen, size_t *written)
+{
+ DWORD dw_written=0;
+ WriteFile(writeCursor, data, (DWORD)datalen, &dw_written, NULL);
+ *written=dw_written;
+ writePosition+=dw_written;
+
+ return 0;
+}
diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.h b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h
new file mode 100644
index 00000000..92102771
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "FileProcessor.h"
+
+class ProgressiveProcessor : public FileProcessor
+{
+public:
+ ProgressiveProcessor();
+ ~ProgressiveProcessor();
+
+private:
+ /* FLVProcessor virtual method overrides */
+ int Write(void *data, size_t datalen, size_t *written);
+private:
+ HANDLE writeCursor;
+ wchar_t tempFile[MAX_PATH];
+}; \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.cpp b/Src/Plugins/Input/in_flv/StreamProcessor.cpp
new file mode 100644
index 00000000..d5331cdc
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/StreamProcessor.cpp
@@ -0,0 +1,112 @@
+#include "StreamProcessor.h"
+#include "FLVHeader.h"
+
+StreamProcessor::StreamProcessor()
+{
+ buffer.reserve(1024*1024);
+ bytesWritten=0;
+ readHeader=false;
+}
+
+int StreamProcessor::Write(void *data, size_t datalen, size_t *written)
+{
+ *written = buffer.write(data, datalen);
+ bytesWritten += *written;
+ if (*written != datalen)
+ return FLVPROCESSOR_WRITE_WAIT; // tell the FLVReader we need a break :)
+ return FLVPROCESSOR_WRITE_OK;
+}
+
+int StreamProcessor::Process()
+{
+ // we're actually not going to parse anything until the GetFrame() call.
+ // since we can't seek anyway
+ // but we do need to validate that this is an FLV stream
+
+ if (!readHeader)
+ {
+ if (buffer.size() >= 9) // see if we have enough data
+ {
+ uint8_t data[9] = {0};
+ buffer.read(data, 9);
+
+ if (header.Read(data, 9))
+ {
+ readHeader=true;
+ return FLV_OK;
+ }
+ else // not an FLV header
+ {
+ return FLV_ERROR;
+ }
+ }
+ }
+
+ return FLV_NEED_MORE_DATA;
+}
+
+uint64_t StreamProcessor::GetProcessedPosition()
+{
+ // since we parse the bitstream on-demand, we'll just return how many bytes we've buffered so far
+ if (readHeader) // make sure we've at least found the main FLV header
+ return bytesWritten;
+ else
+ return 0;
+}
+
+uint32_t StreamProcessor::GetMaxTimestamp()
+{
+ return -1000; // it's a stream! no length!
+}
+
+uint64_t StreamProcessor::Seek(uint64_t position)
+{
+ // we can't really seek in a traditional sense, but since Seek gets called to simply advance the read pointer,
+ // we'll advance within our buffer
+ // we always set FrameData::location to 0, so can just call like this
+ return buffer.advance((size_t)position);
+}
+
+size_t StreamProcessor::Read(void *data, size_t bytes)
+{
+ // easy :)
+ return buffer.read(data, bytes);
+}
+
+// the fun happens here
+bool StreamProcessor::GetFrame(size_t frameIndex, FrameData &frameData)
+{
+ // since this is a stream, we're going to ignore frameIndex and just give them the next frame
+
+ if (buffer.size() >= 15)
+ {
+ uint8_t data[15] = {0};
+ buffer.peek(data, 15);
+ if (frameData.header.Read(data, 15))
+ {
+ // first, make sure we have enough data buffered to read the whole thing
+ // because the next thing to get called after this function is Read()
+ if (frameData.header.dataSize + 15 <= buffer.size())
+ {
+ // since we're streaming and only processing one frame at a time
+ // we're going to set the returned frame's location to 0 (start of the ring buffer)
+ frameData.location = 0;
+ return true;
+ }
+ }
+ }
+ return false; // not ready for a frame yet
+}
+
+bool StreamProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
+{
+ return false; // can't seek!
+}
+
+FLVHeader *StreamProcessor::GetHeader()
+{
+if (readHeader)
+return &header;
+else
+return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.h b/Src/Plugins/Input/in_flv/StreamProcessor.h
new file mode 100644
index 00000000..cd66ccdd
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/StreamProcessor.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "FLVProcessor.h"
+#include "../nu/RingBuffer.h"
+
+class StreamProcessor : public FLVProcessor
+{
+public:
+ StreamProcessor();
+
+private:
+ int Write(void *data, size_t datalen, size_t *written);
+ int Process();
+ uint64_t Seek(uint64_t position);
+ size_t Read(void *data, size_t bytes);
+ uint64_t GetProcessedPosition();
+ bool GetFrame(size_t frameIndex, FrameData &frameData);
+ uint32_t GetMaxTimestamp();
+ bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
+ bool IsStreaming() { return true; }
+ FLVHeader *GetHeader();
+private:
+ RingBuffer buffer;
+ uint64_t bytesWritten;
+ bool readHeader;
+ FLVHeader header;
+};
+
diff --git a/Src/Plugins/Input/in_flv/VideoThread.cpp b/Src/Plugins/Input/in_flv/VideoThread.cpp
new file mode 100644
index 00000000..e07d58d0
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/VideoThread.cpp
@@ -0,0 +1,291 @@
+#include "main.h"
+#include "VideoThread.h"
+#include "api__in_flv.h"
+#include "FLVVideoHeader.h"
+#include <shlwapi.h>
+#include <windows.h>
+#include "../nu/threadname.h"
+#include <api/service/waservicefactory.h>
+#include "../nu/AutoLock.h"
+#include "../nu/SampleQueue.h"
+
+int width, height;
+IVideoOutput *videoOutput=0;
+static HANDLE videoThread=0;
+static volatile LONG video_flush=0;
+static ifc_flvvideodecoder *videoDecoder=0;
+bool video_opened=false;
+static HANDLE coded_frames_event=0;
+static HANDLE video_flush_event=0;
+static Nullsoft::Utility::LockGuard coded_frames_guard;
+extern bool video_only;
+
+void Video_Init()
+{
+ video_opened=false;
+ videoDecoder=0;
+ videoThread=0;
+ width=0;
+ height=0;
+
+ if (coded_frames_event == 0)
+ coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (video_flush_event == 0)
+ video_flush_event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ video_flush=0;
+}
+
+struct FRAMEDATA
+{
+ FRAMEDATA()
+ {
+ data=0;
+ length=0;
+ timestamp=0;
+ }
+
+ ~FRAMEDATA()
+ {
+ free(data);
+ }
+ void Reset()
+ {
+ free(data);
+ data=0;
+ length=0;
+ timestamp=0;
+ }
+ void Set(void *_data, size_t _length, uint32_t ts)
+ {
+ data = _data;
+ length = _length;
+ timestamp = ts;
+ }
+ void *data;
+ size_t length;
+ uint32_t timestamp;
+};
+
+static SampleQueue<FRAMEDATA> coded_frames;
+
+extern int GetOutputTime();
+static void DecodeVideo(FRAMEDATA *framedata)
+{
+ if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
+ {
+ int decodeResult = videoDecoder->DecodeSample(framedata->data, framedata->length, framedata->timestamp);
+
+ if (decodeResult == FLV_VIDEO_SUCCESS)
+ {
+ void *data, *decoder_data;
+ uint64_t timestamp=framedata->timestamp;
+ while (videoDecoder->GetPicture(&data, &decoder_data, &timestamp) == FLV_VIDEO_SUCCESS)
+ {
+ if (!video_opened)
+ {
+ int color_format;
+ if (videoDecoder->GetOutputFormat(&width, &height, &color_format) == FLV_VIDEO_SUCCESS)
+ {
+ videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0);
+ videoOutput->open(width, height, 0, 1.0, color_format);
+ video_opened=true;
+ }
+ }
+ if (video_opened)
+ {
+again:
+ int realTime =(int)GetOutputTime();
+ if (timestamp > (realTime+5))
+ {
+ HANDLE handles[] = {killswitch, video_flush_event};
+ int ret=WaitForMultipleObjects(2, handles, FALSE, (DWORD)(timestamp-realTime));
+ if (ret != WAIT_TIMEOUT)
+ {
+ videoDecoder->FreePicture(data, decoder_data);
+ framedata->Reset();
+ return ;
+ }
+ goto again; // TODO: handle paused state a little better than this
+ }
+ videoOutput->draw(data);
+ }
+ videoDecoder->FreePicture(data, decoder_data);
+ }
+ }
+ }
+
+ framedata->Reset();
+}
+
+DWORD CALLBACK VideoProcedure(LPVOID param)
+{
+ SetThreadName(-1,"FLV_VideoProcedure");
+ HANDLE wait_handles[] = { killswitch, video_flush_event, coded_frames_event};
+ int ret;
+ do
+ {
+ ret = WaitForMultipleObjects(3, wait_handles, FALSE, INFINITE);
+ if (ret == WAIT_OBJECT_0 + 1)
+ {
+ if (video_flush)
+ {
+ InterlockedDecrement(&video_flush);
+ if (videoDecoder)
+ videoDecoder->Flush();
+ }
+ ResetEvent(video_flush_event);
+ }
+ else if (ret == WAIT_OBJECT_0 + 2)
+ {
+ FRAMEDATA *frame_data = 0;
+ while (frame_data = coded_frames.PopProcessed())
+ {
+ DecodeVideo(frame_data);
+ frame_data->Reset();
+ coded_frames.PushFree(frame_data);
+ }
+ }
+ } while (ret != WAIT_OBJECT_0);
+
+ if (video_opened && videoOutput)
+ videoOutput->close();
+ video_opened=false;
+ return 0;
+}
+
+bool Video_IsSupported(int type)
+{
+ size_t n = 0;
+ waServiceFactory *factory = NULL;
+ while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
+ {
+ svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
+ if (creator)
+ {
+ int supported = creator->HandlesVideo(type);
+ factory->releaseInterface(creator);
+ if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
+ return true;
+ }
+ }
+ return false;
+}
+
+static ifc_flvvideodecoder *CreateVideoDecoder(int type)
+{
+ ifc_flvvideodecoder *video_decoder = 0;
+ size_t n = 0;
+ waServiceFactory *factory = NULL;
+ while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
+ {
+ svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
+ if (creator)
+ {
+ if (creator->CreateVideoDecoder(type, width, height, &video_decoder) == FLV_VIDEO_SUCCESS)
+ return video_decoder;
+
+ factory->releaseInterface(creator);
+ }
+ }
+ return 0;
+}
+// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
+static const GUID playbackConfigGroupGUID =
+{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
+bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp)
+{
+ if (!videoDecoder)
+ {
+ videoDecoder = CreateVideoDecoder(type);
+ }
+
+ if (videoDecoder)
+ {
+ if (!video_only && !videoThread)
+ {
+ DWORD threadId;
+ videoThread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId);
+ SetThreadPriority(videoThread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
+ }
+
+ FRAMEDATA *new_frame = coded_frames.PopFree();
+ if (new_frame)
+ {
+ new_frame->Set(data, length, timestamp);
+ if (video_only)
+ {
+ DecodeVideo(new_frame);
+ new_frame->Reset();
+ coded_frames.PushFree(new_frame);
+ }
+ else
+ {
+ coded_frames.PushProcessed(new_frame);
+ SetEvent(coded_frames_event);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void Video_Stop()
+{
+ if (video_only)
+ {
+ ResetEvent(coded_frames_event);
+ Nullsoft::Utility::AutoLock l(coded_frames_guard);
+ coded_frames.Trim();
+ if (video_opened && videoOutput)
+ videoOutput->close();
+ video_opened=false;
+ }
+ else
+ {
+ if (videoThread)
+ WaitForSingleObject(videoThread, INFINITE);
+ videoThread=0;
+
+ InterlockedIncrement(&video_flush);
+ ResetEvent(coded_frames_event);
+ Nullsoft::Utility::AutoLock l(coded_frames_guard);
+ coded_frames.Trim();
+ }
+}
+
+void Video_Close()
+{
+ video_opened=false;
+
+ if (videoDecoder)
+ {
+ videoDecoder->Close();
+ videoDecoder=0;
+ }
+}
+
+void VideoFlush()
+{
+ if (video_only)
+ {
+ if (videoDecoder)
+ videoDecoder->Flush();
+ }
+ else if (videoThread)
+ {
+ InterlockedIncrement(&video_flush);
+ ResetEvent(coded_frames_event);
+ coded_frames.Trim();
+ SetEvent(video_flush_event);
+ }
+}
+
+bool Video_DecoderReady()
+{
+ if (!videoDecoder)
+ return true;
+
+ return !!videoDecoder->Ready();
+}
diff --git a/Src/Plugins/Input/in_flv/VideoThread.h b/Src/Plugins/Input/in_flv/VideoThread.h
new file mode 100644
index 00000000..3d2d7487
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/VideoThread.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_IN_FLV_VIDEOTHREAD_H
+#define NULLSOFT_IN_FLV_VIDEOTHREAD_H
+
+#include "../nsv/dec_if.h"
+#include "../Winamp/wa_ipc.h"
+#include "svc_flvdecoder.h"
+#include "ifc_flvvideodecoder.h"
+
+bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp);
+void VideoFlush();
+extern int width, height;
+extern IVideoOutput *videoOutput;
+extern bool video_opened;
+
+void Video_Init();
+void Video_Stop();
+void Video_Close();
+bool Video_IsSupported(int type);
+bool Video_DecoderReady();
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/api__in_flv.h b/Src/Plugins/Input/in_flv/api__in_flv.h
new file mode 100644
index 00000000..911b9eb2
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/api__in_flv.h
@@ -0,0 +1,12 @@
+#ifndef NULLSOFT_IN_FLV_API_H
+#define NULLSOFT_IN_FLV_API_H
+
+#include "../Agave/Config/api_config.h"
+extern api_config *AGAVE_API_CONFIG;
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#endif // !NULLSOFT_IN_FLV_API_H
diff --git a/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h
new file mode 100644
index 00000000..66ae264b
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h
@@ -0,0 +1,62 @@
+#pragma once
+
+enum
+{
+ FLV_AUDIO_SUCCESS = 0,
+ FLV_AUDIO_FAILURE = 1,
+ FLV_AUDIO_NEEDS_MORE_INPUT = 2,
+};
+
+class ifc_flvaudiodecoder : public Dispatchable
+{
+protected:
+ ifc_flvaudiodecoder() {}
+ ~ifc_flvaudiodecoder() {}
+public:
+ int GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits);
+ int DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate);
+ void Flush();
+ void Close();
+ int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it
+ void SetPreferences(unsigned int max_channels, unsigned int preferred_bits);
+ DISPATCH_CODES
+ {
+ FLV_AUDIO_GETOUTPUTFORMAT = 0,
+ FLV_AUDIO_DECODE = 1,
+ FLV_AUDIO_FLUSH = 2,
+ FLV_AUDIO_CLOSE = 3,
+ FLV_AUDIO_READY = 4,
+ FLV_AUDIO_SETPREFERENCES=5,
+ };
+};
+
+inline int ifc_flvaudiodecoder::GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits)
+{
+ return _call(FLV_AUDIO_GETOUTPUTFORMAT, (int)FLV_AUDIO_FAILURE, sample_rate, channels, bits);
+}
+
+inline int ifc_flvaudiodecoder::DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate)
+{
+ return _call(FLV_AUDIO_DECODE, (int)FLV_AUDIO_FAILURE, input_buffer, input_buffer_bytes, samples, samples_size_bytes, bitrate);
+}
+
+inline void ifc_flvaudiodecoder::Flush()
+{
+ _voidcall(FLV_AUDIO_FLUSH);
+}
+
+inline void ifc_flvaudiodecoder::Close()
+{
+ _voidcall(FLV_AUDIO_CLOSE);
+}
+
+inline int ifc_flvaudiodecoder::Ready()
+{
+ return _call(FLV_AUDIO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking
+}
+
+inline void ifc_flvaudiodecoder::SetPreferences(unsigned int max_channels, unsigned int preferred_bits)
+{
+ _voidcall(FLV_AUDIO_SETPREFERENCES, max_channels, preferred_bits);
+}
+
diff --git a/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h
new file mode 100644
index 00000000..1ee09833
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h
@@ -0,0 +1,67 @@
+#pragma once
+
+enum
+{
+ FLV_VIDEO_SUCCESS = 0,
+ FLV_VIDEO_FAILURE = 1,
+};
+
+class ifc_flvvideodecoder : public Dispatchable
+{
+protected:
+ ifc_flvvideodecoder() {}
+ ~ifc_flvvideodecoder() {}
+public:
+ int GetOutputFormat(int *x, int *y, int *color_format);
+ int DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp);
+ void Flush();
+ void Close();
+ int GetPicture(void **data, void **decoder_data, uint64_t *timestamp);
+ void FreePicture(void *data, void *decoder_data);
+ int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it
+ DISPATCH_CODES
+ {
+ FLV_VIDEO_GETOUTPUTFORMAT = 0,
+ FLV_VIDEO_DECODE = 1,
+ FLV_VIDEO_FLUSH = 2,
+ FLV_VIDEO_CLOSE = 3,
+ FLV_VIDEO_GET_PICTURE = 4,
+ FLV_VIDEO_FREE_PICTURE = 5,
+ FLV_VIDEO_READY = 6,
+ };
+};
+
+inline int ifc_flvvideodecoder::GetOutputFormat(int *x, int *y, int *color_format)
+{
+ return _call(FLV_VIDEO_GETOUTPUTFORMAT, (int)FLV_VIDEO_FAILURE, x, y, color_format);
+}
+
+inline int ifc_flvvideodecoder::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp)
+{
+ return _call(FLV_VIDEO_DECODE, (int)FLV_VIDEO_FAILURE, inputBuffer, inputBufferBytes, timestamp);
+}
+
+inline void ifc_flvvideodecoder::Flush()
+{
+ _voidcall(FLV_VIDEO_FLUSH);
+}
+
+inline void ifc_flvvideodecoder::Close()
+{
+ _voidcall(FLV_VIDEO_CLOSE);
+}
+
+inline int ifc_flvvideodecoder::GetPicture(void **data, void **decoder_data, uint64_t *timestamp)
+{
+ return _call(FLV_VIDEO_GET_PICTURE, (int)FLV_VIDEO_FAILURE, data, decoder_data, timestamp);
+}
+
+inline void ifc_flvvideodecoder::FreePicture(void *data, void *decoder_data)
+{
+ _voidcall(FLV_VIDEO_FREE_PICTURE, data, decoder_data);
+}
+
+inline int ifc_flvvideodecoder::Ready()
+{
+ return _call(FLV_VIDEO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking
+}
diff --git a/Src/Plugins/Input/in_flv/in_flv.rc b/Src/Plugins/Input/in_flv/in_flv.rc
new file mode 100644
index 00000000..61854c33
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/in_flv.rc
@@ -0,0 +1,85 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_FLV "Nullsoft Flash Video Decoder v%s"
+ 65535 "{EC959D43-9122-4807-B928-7B46207AFA49}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_FLV_OLD "Nullsoft Flash Video Decoder"
+ IDS_BUFFERING "Buffering"
+ IDS_FLASH_VIDEO "Flash Video"
+ IDS_VP6_FLASH_VIDEO "VP6 Flash Video"
+ IDS_FAMILY_STRING "Flash Video"
+ IDS_ABOUT_TEXT "%s\n© 2006-2023 Winamp SA\nWritten by: Ben Allison\nBuild date: %s"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Input/in_flv/in_flv.sln b/Src/Plugins/Input/in_flv/in_flv.sln
new file mode 100644
index 00000000..7bdd0028
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/in_flv.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29609.76
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_flv", "in_flv.vcxproj", "{405DF558-062D-43AB-B7FB-02C4CF8B28CE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.ActiveCfg = Debug|Win32
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.Build.0 = Debug|Win32
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.ActiveCfg = Debug|x64
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.Build.0 = Debug|x64
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.ActiveCfg = Release|Win32
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.Build.0 = Release|Win32
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.ActiveCfg = Release|x64
+ {405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B6180127-9081-4796-819C-0E19EBFB2BC4}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Input/in_flv/in_flv.vcxproj b/Src/Plugins/Input/in_flv/in_flv.vcxproj
new file mode 100644
index 00000000..cd373c26
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/in_flv.vcxproj
@@ -0,0 +1,295 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{405DF558-062D-43AB-B7FB-02C4CF8B28CE}</ProjectGuid>
+ <RootNamespace>in_flv</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnabled>false</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <SmallerTypeCheck>false</SmallerTypeCheck>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <SmallerTypeCheck>false</SmallerTypeCheck>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <SmallerTypeCheck>false</SmallerTypeCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\nu\RingBuffer.cpp" />
+ <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" />
+ <ClCompile Include="AMFDispatch.cpp" />
+ <ClCompile Include="AMFObject.cpp" />
+ <ClCompile Include="BackgroundDownloader.cpp" />
+ <ClCompile Include="ExtendedInfo.cpp" />
+ <ClCompile Include="FileProcessor.cpp" />
+ <ClCompile Include="FLVAudioHeader.cpp" />
+ <ClCompile Include="FLVCOM.cpp" />
+ <ClCompile Include="FLVHeader.cpp" />
+ <ClCompile Include="FLVMetadata.cpp" />
+ <ClCompile Include="FLVProcessor.cpp" />
+ <ClCompile Include="FLVReader.cpp" />
+ <ClCompile Include="FLVStreamHeader.cpp" />
+ <ClCompile Include="FLVVideoHeader.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="PlayThread.cpp" />
+ <ClCompile Include="ProgressiveProcessor.cpp" />
+ <ClCompile Include="StreamProcessor.cpp" />
+ <ClCompile Include="VideoThread.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\RingBuffer.h" />
+ <ClInclude Include="..\..\..\nu\SpillBuffer.h" />
+ <ClInclude Include="..\..\..\nu\VideoClock.h" />
+ <ClInclude Include="AMFDispatch.h" />
+ <ClInclude Include="AMFObject.h" />
+ <ClInclude Include="api__in_flv.h" />
+ <ClInclude Include="BackgroundDownloader.h" />
+ <ClInclude Include="FileProcessor.h" />
+ <ClInclude Include="FLVAudioHeader.h" />
+ <ClInclude Include="FLVCOM.h" />
+ <ClInclude Include="FLVHeader.h" />
+ <ClInclude Include="FLVMetadata.h" />
+ <ClInclude Include="FLVProcessor.h" />
+ <ClInclude Include="FLVReader.h" />
+ <ClInclude Include="FLVStreamHeader.h" />
+ <ClInclude Include="FLVUtil.h" />
+ <ClInclude Include="FLVVideoHeader.h" />
+ <ClInclude Include="ifc_flvaudiodecoder.h" />
+ <ClInclude Include="ifc_flvvideodecoder.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="ProgressiveProcessor.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="StreamProcessor.h" />
+ <ClInclude Include="svc_flvdecoder.h" />
+ <ClInclude Include="VideoThread.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="in_flv.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters b/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters
new file mode 100644
index 00000000..8e199126
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AMFDispatch.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AMFObject.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="BackgroundDownloader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ExtendedInfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FileProcessor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVAudioHeader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVHeader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVMetadata.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVProcessor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVReader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVStreamHeader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FLVVideoHeader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlayThread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ProgressiveProcessor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="StreamProcessor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="VideoThread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\RingBuffer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\SpillBuffer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="VideoThread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="svc_flvdecoder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="StreamProcessor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ProgressiveProcessor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_flvvideodecoder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_flvaudiodecoder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVVideoHeader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVStreamHeader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVReader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVProcessor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVMetadata.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVAudioHeader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVHeader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FLVCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FileProcessor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="BackgroundDownloader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__in_flv.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AMFObject.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AMFDispatch.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\RingBuffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\SpillBuffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\VideoClock.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{9916d7e8-2b6f-493b-8618-234dec0cefeb}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{888ab1e4-8d93-40b5-92f8-35339ac6179a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{0f72d524-22df-4752-8f22-5c1db90c62b0}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="in_flv.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/main.cpp b/Src/Plugins/Input/in_flv/main.cpp
new file mode 100644
index 00000000..d51d0b2b
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/main.cpp
@@ -0,0 +1,432 @@
+#include "main.h"
+#include <shlwapi.h>
+#include "api__in_flv.h"
+#include <stdio.h>
+#include <api/service/waservicefactory.h>
+#include "../Winamp/wa_ipc.h"
+#include "resource.h"
+#include "FileProcessor.h"
+#include "FLVCOM.h"
+#include "VideoThread.h"
+#include "../f263/flv_f263_decoder.h"
+#include <strsafe.h>
+
+#define FLV_PLUGIN_VERSION L"1.47"
+
+template <class api_T>
+static void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+static void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+/* Wasabi services */
+api_application *WASABI_API_APP=0;
+api_config *AGAVE_API_CONFIG=0;
+api_language *WASABI_API_LNG = 0;
+
+In_Module *swf_mod = 0;
+HMODULE in_swf = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+wchar_t pluginName[256] = {0};
+wchar_t *playFile=0;
+HANDLE killswitch, playthread=0;
+int g_length=-1000;
+bool video_only=false;
+int paused = 0;
+nu::VideoClock video_clock;
+int m_need_seek=-1;
+extern uint32_t last_timestamp;
+int pan = 0, volume = -666;
+wchar_t *stream_title=0;
+Nullsoft::Utility::LockGuard stream_title_guard;
+
+// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
+static const GUID playbackConfigGroupGUID =
+{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
+
+void SetFileExtensions(void)
+{
+ static char fileExtensionsString[256] = {0}; // "FLV\0Flash Video\0"
+ char* end = 0;
+ size_t remaining;
+ StringCchCopyExA(fileExtensionsString, 256, "FLV", &end, &remaining, 0);
+ StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_FLASH_VIDEO), 0, 0, 0);
+ plugin.FileExtensions = fileExtensionsString;
+}
+
+int Init()
+{
+ if (!IsWindow(plugin.hMainWindow))
+ return IN_INIT_FAILURE;
+
+ ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,InFlvLangGUID);
+
+ if (plugin.service->service_getServiceByGuid(flv_h263_guid))
+ StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION L" (h)");
+ else
+ StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION);
+ plugin.description = (char*)pluginName;
+ SetFileExtensions();
+
+ DispatchInfo flvDisp = { L"FLV", &flvCOM };
+ SendMessage(plugin.hMainWindow, WM_WA_IPC, (WPARAM)&flvDisp, IPC_ADD_DISPATCH_OBJECT);
+
+ killswitch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ return IN_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ CloseHandle(killswitch);
+ ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ if (in_swf)
+ FreeLibrary(in_swf);
+}
+
+void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms)
+{
+ if (!file || !*file)
+ {
+ if (swf_mod)
+ {
+ swf_mod->GetFileInfo(file, title, length_in_ms);
+ return;
+ }
+
+ file = playFile;
+ }
+
+ // no title support as it's not always stored in a standard way in FLV
+ if (title)
+ {
+ Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
+ if (stream_title && (!file || !file[0]))
+ {
+ lstrcpyn(title, stream_title, GETFILEINFO_TITLE_LENGTH);
+ }
+ else
+ {
+ lstrcpyn(title, file ? file : L"", GETFILEINFO_TITLE_LENGTH);
+ PathStripPath(title);
+ }
+ }
+
+ // calculate length
+ if (file == playFile) // currently playing song?
+ *length_in_ms = g_length; // easy!
+ else if (PathIsURLW(file)) // don't calculate lengths for URLs since we'd have to connect
+ {
+ *length_in_ms = -1;
+ }
+ else
+ {
+ // open the file and find the "duration" metadata
+ FileProcessor processor(file);
+
+ size_t frameIndex=0;
+ FrameData frameData;
+ int length=-1;
+ bool found=false;
+ do
+ {
+ // enumerate the frames as we process
+ // this function will fail the first few times
+ // because Process() hasn't been called enough
+ if (processor.GetFrame(frameIndex, frameData))
+ {
+ if (frameData.header.type == FLV::FRAME_TYPE_METADATA)
+ {
+ if (processor.Seek(frameData.location + 15) == -1)
+ break;
+
+ size_t dataSize = frameData.header.dataSize;
+ uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
+ if (metadatadata)
+ {
+ size_t bytesRead = processor.Read(metadatadata, dataSize);
+ if (bytesRead != dataSize)
+ {
+ free(metadatadata);
+ break;
+ }
+ FLVMetadata metadata;
+ metadata.Read(metadatadata, dataSize);
+
+ for ( FLVMetadata::Tag *tag : metadata.tags )
+ {
+ if ( !_wcsicmp( tag->name.str, L"onMetaData" ) )
+ {
+ AMFType *duration = tag->parameters->array[ L"duration" ];
+ if ( duration )
+ {
+ length = (int)( AMFGetDouble( duration ) * 1000.0 );
+ found = true;
+ }
+ }
+ }
+ free(metadatadata);
+ }
+ else
+ break;
+ }
+ frameIndex++;
+ }
+ }
+ while (!found && processor.Process() == FLV_OK); // for local files, any return value other than FLV_OK is a failure
+ *length_in_ms = length;
+ }
+}
+
+int InfoBox(const wchar_t *file, HWND hwndParent)
+{
+ return INFOBOX_UNCHANGED;
+}
+
+int IsOurFile(const wchar_t *file)
+{
+ return 0;
+}
+
+int Play(const wchar_t *file)
+{
+ {
+ Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
+ free(stream_title);
+ stream_title=0;
+ }
+ if (!videoOutput)
+ videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
+
+ video_only=false;
+ m_need_seek = -1;
+ video_clock.Reset();
+ paused=0;
+ ResetEvent(killswitch);
+ free(playFile);
+ playFile = _wcsdup(file);
+ playthread=CreateThread(0, 0, PlayProcedure, 0, 0, 0);
+ SetThreadPriority(playthread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
+ return 0; // success
+}
+
+
+void Pause()
+{
+ paused = 1;
+ if (swf_mod)
+ {
+ swf_mod->Pause();
+ }
+ else if (video_only)
+ {
+ video_clock.Pause();
+ }
+ else
+ {
+ plugin.outMod->Pause(1);
+ }
+}
+
+void UnPause()
+{
+ paused = 0;
+ if (swf_mod)
+ {
+ swf_mod->UnPause();
+ }
+ else if (video_only)
+ {
+ video_clock.Unpause();
+ }
+ else
+ {
+ plugin.outMod->Pause(0);
+ }
+
+}
+
+int IsPaused()
+{
+ if (swf_mod)
+ return swf_mod->IsPaused();
+
+ return paused;
+}
+
+void Stop()
+{
+ if (swf_mod)
+ {
+ swf_mod->Stop();
+ swf_mod=0;
+ return;
+ }
+ SetEvent(killswitch);
+ WaitForSingleObject(playthread, INFINITE);
+ playthread=0;
+ plugin.outMod->Close();
+ plugin.SAVSADeInit();
+ paused=0;
+}
+
+int GetLength()
+{
+ if (swf_mod)
+ {
+ return swf_mod->GetLength();
+ }
+ return g_length;
+}
+
+int GetOutputTime()
+{
+ if (swf_mod)
+ {
+ return swf_mod->GetOutputTime();
+ }
+ else if (video_only)
+ {
+ return video_clock.GetOutputTime();
+ }
+ else if (plugin.outMod)
+ {
+ return plugin.outMod->GetOutputTime();
+ }
+ else
+ return 0;
+}
+
+void SetOutputTime(int time_in_ms)
+{
+ if (swf_mod)
+ {
+ swf_mod->SetOutputTime(time_in_ms);
+ return ;
+ }
+ m_need_seek=time_in_ms;
+}
+
+
+void SetVolume(int _volume)
+{
+ if (swf_mod)
+ {
+ swf_mod->SetVolume(_volume);
+ return ;
+ }
+ volume = _volume;
+ if (plugin.outMod)
+ plugin.outMod->SetVolume(volume);
+}
+
+void SetPan(int _pan)
+{
+ if (swf_mod)
+ {
+ swf_mod->SetPan(_pan);
+ return ;
+ }
+ pan = _pan;
+ if (plugin.outMod)
+ plugin.outMod->SetPan(pan);
+}
+
+void EQSet(int on, char data[10], int preamp)
+{
+ if (swf_mod)
+ {
+ swf_mod->EQSet(on, data, preamp);
+ return;
+ }
+}
+
+int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message)
+{
+ MSGBOXPARAMS msgbx = {sizeof(MSGBOXPARAMS),0};
+ msgbx.lpszText = message;
+ msgbx.lpszCaption = title;
+ msgbx.lpszIcon = MAKEINTRESOURCE(102);
+ msgbx.hInstance = GetModuleHandle(0);
+ msgbx.dwStyle = MB_USERICON;
+ msgbx.hwndOwner = parent;
+ return MessageBoxIndirect(&msgbx);
+}
+
+void About(HWND hwndParent)
+{
+ wchar_t message[1024] = {0}, text[1024] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_FLV_OLD,text,1024);
+ StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT),
+ plugin.description, TEXT(__DATE__));
+ DoAboutMessageBox(hwndParent,text,message);
+}
+
+In_Module plugin =
+{
+ IN_VER_RET,
+ "nullsoft(in_flv.dll)",
+ 0,
+ 0,
+ 0 /*"FLV\0Flash Video\0"*/,
+ 1, // not seekable, for now
+ 1,
+ About,
+ About,
+ Init,
+ Quit,
+ GetFileInfo,
+ InfoBox,
+ IsOurFile,
+ Play,
+ Pause,
+ UnPause,
+ IsPaused,
+ Stop,
+ GetLength,
+ GetOutputTime,
+ SetOutputTime,
+ SetVolume,
+ SetPan,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ EQSet,
+ 0,
+ 0
+};
+
+extern "C" __declspec(dllexport) In_Module * winampGetInModule2()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/main.h b/Src/Plugins/Input/in_flv/main.h
new file mode 100644
index 00000000..ba3f4376
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/main.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_IN_FLV_MAIN_H
+#define NULLSOFT_IN_FLV_MAIN_H
+
+#include <windows.h>
+#include "../Winamp/in2.h"
+#include "../nu/VideoClock.h"
+#include "../nu/AutoLock.h"
+
+/* main.cpp */
+extern In_Module plugin;
+extern In_Module *swf_mod;
+extern HMODULE in_swf;
+extern wchar_t *playFile;
+extern HANDLE killswitch;
+extern int m_need_seek;
+extern int paused;
+extern int g_length;
+extern nu::VideoClock video_clock;
+extern wchar_t *stream_title;
+extern Nullsoft::Utility::LockGuard stream_title_guard;
+
+/* PlayThread.cpp */
+
+DWORD CALLBACK PlayProcedure(LPVOID param);
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/resource.h b/Src/Plugins/Input/in_flv/resource.h
new file mode 100644
index 00000000..22a49a42
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/resource.h
@@ -0,0 +1,23 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by in_flv.rc
+//
+#define IDS_NULLSOFT_FLV_OLD 0
+#define IDS_BUFFERING 2
+#define IDS_FLASH_VIDEO 3
+#define IDS_STRING4 4
+#define IDS_VP6_FLASH_VIDEO 4
+#define IDS_FAMILY_STRING 5
+#define IDS_ABOUT_TEXT 6
+#define IDS_NULLSOFT_FLV 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 105
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Input/in_flv/svc_flvdecoder.h b/Src/Plugins/Input/in_flv/svc_flvdecoder.h
new file mode 100644
index 00000000..1093d18a
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/svc_flvdecoder.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include <api/service/services.h>
+
+class ifc_flvaudiodecoder;
+class ifc_flvvideodecoder;
+class svc_flvdecoder : public Dispatchable
+{
+protected:
+ svc_flvdecoder() {}
+ ~svc_flvdecoder() {}
+public:
+ static FOURCC getServiceType() { return WaSvc::FLVDECODER; }
+ enum
+ {
+ CREATEDECODER_SUCCESS = 0,
+ CREATEDECODER_NOT_MINE = -1, // graceful failure
+ CREATEDECODER_FAILURE = 1, // generic failure - format_type is ours but we weren't able to create the decoder
+ };
+ int CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder);
+ int CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder);
+ int HandlesAudio(int format_type);
+ int HandlesVideo(int format_type);
+ DISPATCH_CODES
+ {
+ CREATE_AUDIO_DECODER = 0,
+ CREATE_VIDEO_DECODER = 1,
+ HANDLES_AUDIO=2,
+ HANDLES_VIDEO=3,
+ };
+};
+
+inline int svc_flvdecoder::CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder)
+{
+ return _call(CREATE_AUDIO_DECODER, (int)CREATEDECODER_NOT_MINE, stereo, bits, sample_rate, format, decoder);
+}
+
+inline int svc_flvdecoder::CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder)
+{
+ return _call(CREATE_VIDEO_DECODER, (int)CREATEDECODER_NOT_MINE, format_type,width, height, decoder);
+}
+
+inline int svc_flvdecoder::HandlesAudio(int format_type)
+{
+ return _call(HANDLES_AUDIO, (int)CREATEDECODER_NOT_MINE, format_type);
+}
+
+inline int svc_flvdecoder::HandlesVideo(int format_type)
+{
+ return _call(HANDLES_VIDEO, (int)CREATEDECODER_NOT_MINE, format_type);
+} \ No newline at end of file
diff --git a/Src/Plugins/Input/in_flv/version.rc2 b/Src/Plugins/Input/in_flv/version.rc2
new file mode 100644
index 00000000..871c3650
--- /dev/null
+++ b/Src/Plugins/Input/in_flv/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,47,0,0
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Input Plug-in"
+ VALUE "FileVersion", "1,47,0,0"
+ VALUE "InternalName", "Nullsoft Flash Video Decoder"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "in_flv.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END