2475 lines
68 KiB
C++
2475 lines
68 KiB
C++
|
/***************************************************************************\
|
||
|
*
|
||
|
* (C) copyright Fraunhofer - IIS (1998)
|
||
|
* All Rights Reserved
|
||
|
*
|
||
|
* filename: giofile.cpp
|
||
|
* project : MPEG Decoder
|
||
|
* author : Martin Sieler
|
||
|
* date : 1998-02-11
|
||
|
* contents/description: file I/O class for MPEG Decoder
|
||
|
*
|
||
|
*
|
||
|
\***************************************************************************/
|
||
|
|
||
|
/* ------------------------ includes --------------------------------------*/
|
||
|
|
||
|
#include "main.h"
|
||
|
#include "api__in_mp3.h"
|
||
|
#include <time.h>
|
||
|
#include <locale.h>
|
||
|
#include "../Winamp/wa_ipc.h"
|
||
|
|
||
|
#include "LAMEinfo.h"
|
||
|
#include "OFL.h"
|
||
|
#include "../nu/AutoChar.h"
|
||
|
#include "../nu/AutoWide.h"
|
||
|
|
||
|
#include <api/service/waservicefactory.h>
|
||
|
#include <shlwapi.h>
|
||
|
#include "MP3Info.h"
|
||
|
#include "config.h"
|
||
|
#include <math.h>
|
||
|
#include "id3.h"
|
||
|
#include "../apev2/header.h"
|
||
|
#include "uvox_3902.h"
|
||
|
#include <foundation/error.h>
|
||
|
#include <strsafe.h>
|
||
|
|
||
|
#define MAX_REDIRECTS 10
|
||
|
|
||
|
#define SAFE_MALLOC_MATH(orig, x) (((orig)<x)?x:orig)
|
||
|
// seems like a reasonable limit, but we should check with tag first
|
||
|
#define UVOX_MAXMSG_CAP 1048576
|
||
|
|
||
|
static jnl_connection_t CreateConnection(const char *url, jnl_dns_t dns, size_t sendbufsize, size_t recvbufsize)
|
||
|
{
|
||
|
if (!_strnicmp(url, "https:", strlen("https:")))
|
||
|
return jnl_sslconnection_create(dns, sendbufsize, recvbufsize);
|
||
|
else
|
||
|
return jnl_connection_create(dns, sendbufsize, recvbufsize);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
HWND GetDialogBoxParent()
|
||
|
{
|
||
|
HWND parent = (HWND)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT);
|
||
|
if (!parent || parent == (HWND)1)
|
||
|
return mod.hMainWindow;
|
||
|
return parent;
|
||
|
}
|
||
|
|
||
|
/*-------------------------- defines --------------------------------------*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
//
|
||
|
// CGioFile
|
||
|
//
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// constructor
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
|
||
|
static char ultravoxUserAgent[128] = "";
|
||
|
char *GetUltravoxUserAgent()
|
||
|
{
|
||
|
if (!ultravoxUserAgent[0])
|
||
|
{
|
||
|
StringCchPrintfA(ultravoxUserAgent, 128, "User-Agent: WinampMPEG/%01x.%02x, Ultravox/2.1\r\n"
|
||
|
"Ultravox-transport-type: TCP\r\n",
|
||
|
WINAMP_VERSION_MAJOR(winampVersion),
|
||
|
WINAMP_VERSION_MINOR(winampVersion));
|
||
|
}
|
||
|
return ultravoxUserAgent;
|
||
|
}
|
||
|
|
||
|
static char userAgent[128] = "";
|
||
|
char *GetUserAgent()
|
||
|
{
|
||
|
if (!userAgent[0])
|
||
|
{
|
||
|
|
||
|
StringCchPrintfA(userAgent, 128, "User-Agent: WinampMPEG/%01x.%02x\r\n",
|
||
|
WINAMP_VERSION_MAJOR(winampVersion),
|
||
|
WINAMP_VERSION_MINOR(winampVersion));
|
||
|
}
|
||
|
return userAgent;
|
||
|
}
|
||
|
|
||
|
extern int lastfn_status_err;
|
||
|
|
||
|
CGioFile::CGioFile()
|
||
|
{
|
||
|
req=0;
|
||
|
proxy_host=0;
|
||
|
host=0;
|
||
|
request=0;
|
||
|
proxy_lp=0;
|
||
|
lpinfo=0;
|
||
|
mpeg_length=0;
|
||
|
file_position=0;
|
||
|
mpeg_position=0;
|
||
|
no_peek_hack=false;
|
||
|
encodingMethod=0;
|
||
|
uvox_3901=0;
|
||
|
uvox_3902=0;
|
||
|
stream_url[0]=0;
|
||
|
stream_genre[0]=0;
|
||
|
stream_current_title[0]=0;
|
||
|
stream_name[0]=0;
|
||
|
ZeroMemory(&last_write_time, sizeof(last_write_time));
|
||
|
ZeroMemory(id3v1_data, sizeof(id3v1_data));
|
||
|
lengthVerified=false;
|
||
|
m_vbr_bytes = 0;
|
||
|
uvox_last_message = 0;
|
||
|
prepad = 0;
|
||
|
postpad = 0;
|
||
|
m_vbr_ms = 0;
|
||
|
m_vbr_hdr = 0;
|
||
|
stream_id3v2_buf = 0;
|
||
|
lyrics3_size=0;
|
||
|
lyrics3_data=0;
|
||
|
apev2_data=0;
|
||
|
m_content_type = 0;
|
||
|
ZeroMemory(uvox_meta, sizeof(uvox_meta));
|
||
|
is_uvox = 0;
|
||
|
uvox_sid = uvox_maxbr = uvox_avgbr = 0;
|
||
|
uvox_message_cnt = uvox_desyncs = 0;
|
||
|
uvox_stream_data = 0;
|
||
|
uvox_stream_data_len = 0;
|
||
|
ZeroMemory(&uvox_artwork, sizeof(uvox_artwork));
|
||
|
uvox_maxmsg = 65535;
|
||
|
force_lpinfo[0] = 0;
|
||
|
is_stream_seek = 0;
|
||
|
last_full_url[0] = 0;
|
||
|
m_is_stream = 0;
|
||
|
m_redircnt = 0;
|
||
|
m_auth_tries = 0;
|
||
|
m_full_buffer = NULL;
|
||
|
fEof = false;
|
||
|
m_connection = NULL;
|
||
|
m_dns = NULL;
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
m_http_response = 0;
|
||
|
m_seek_reset = false;
|
||
|
m_is_stream_seek = false;
|
||
|
m_is_stream_seekable = false;
|
||
|
initTitleList();
|
||
|
m_vbr_frames = 0;
|
||
|
ZeroMemory(&m_vbr_toc, sizeof(m_vbr_toc));
|
||
|
m_vbr_frame_len = 0;
|
||
|
m_vbr_flag = 0;
|
||
|
m_id3v2_len = 0;
|
||
|
m_id3v1_len = 0;
|
||
|
port = 80;
|
||
|
constate = 0;
|
||
|
ZeroMemory(&save_filename, sizeof(save_filename));
|
||
|
ZeroMemory(&server_name, sizeof(server_name));
|
||
|
recvbuffersize = 32768;
|
||
|
|
||
|
timeout_start = GetTickCount();
|
||
|
meta_interval = 0;
|
||
|
meta_pos = 0;
|
||
|
ZeroMemory(&stream_title_save, sizeof(stream_title_save));
|
||
|
ZeroMemory(&last_title_sent, sizeof(last_title_sent));
|
||
|
ZeroMemory(&dlg_realm, sizeof(dlg_realm));
|
||
|
m_useaproxy = 0;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// destructor
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
CGioFile::~CGioFile()
|
||
|
{
|
||
|
int x, y;
|
||
|
for (x = 0; x < 2; x ++)
|
||
|
for (y = 0; y < 32; y ++)
|
||
|
free(uvox_meta[x][y]);
|
||
|
free(m_content_type);
|
||
|
free(uvox_stream_data);
|
||
|
free(stream_id3v2_buf);
|
||
|
free(lyrics3_data);
|
||
|
free(apev2_data);
|
||
|
free(uvox_3901);
|
||
|
free(uvox_3902);
|
||
|
|
||
|
Close();
|
||
|
delete m_vbr_hdr;
|
||
|
m_vbr_hdr = 0;
|
||
|
clearTitleList();
|
||
|
free(lpinfo);
|
||
|
free(proxy_lp);
|
||
|
free(request);
|
||
|
free(host);
|
||
|
free(req);
|
||
|
free(proxy_host);
|
||
|
}
|
||
|
|
||
|
static bool GetRG(const char *key, ID3v2 &info, APEv2::Tag &apev2, wchar_t *val, int len)
|
||
|
{
|
||
|
val[0]=0;
|
||
|
if (info.GetString(key, val, len) == 1 && val[0])
|
||
|
return true;
|
||
|
if (apev2.GetString(key, val, len) == APEv2::APEV2_SUCCESS && val[0])
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
float CGioFile::GetGain()
|
||
|
{
|
||
|
if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false))
|
||
|
{
|
||
|
// if (info.HasData())
|
||
|
{
|
||
|
float dB = 0, peak = 1.0f;
|
||
|
wchar_t gain[128] = L"", peakVal[128] = L"";
|
||
|
_locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
|
||
|
|
||
|
switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0))
|
||
|
{
|
||
|
case 0: // track
|
||
|
if ((GetRG("replaygain_track_gain", info, apev2, gain, 128) == false || !gain[0])
|
||
|
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
|
||
|
GetRG("replaygain_album_gain", info, apev2, gain, 128);
|
||
|
|
||
|
if ((GetRG("replaygain_track_peak", info, apev2, peakVal, 128) == false || !peakVal[0])
|
||
|
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
|
||
|
GetRG("replaygain_album_peak", info, apev2, peakVal, 128);
|
||
|
break;
|
||
|
case 1:
|
||
|
if ((GetRG("replaygain_album_gain", info, apev2, gain, 128) == false || !gain[0])
|
||
|
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
|
||
|
GetRG("replaygain_track_gain", info, apev2, gain, 128);
|
||
|
|
||
|
if ((GetRG("replaygain_album_peak", info, apev2, peakVal, 128) == false || !peakVal[0])
|
||
|
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
|
||
|
GetRG("replaygain_track_peak", info, apev2, peakVal, 128);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (gain[0])
|
||
|
{
|
||
|
if (gain[0] == L'+')
|
||
|
dB = (float)_wtof_l(&gain[1], C_locale);
|
||
|
else
|
||
|
dB = (float)_wtof_l(gain, C_locale);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0);
|
||
|
return (float)pow(10.0f, dB / 20.0f);
|
||
|
}
|
||
|
|
||
|
if (peakVal[0])
|
||
|
{
|
||
|
peak = (float)_wtof_l(peakVal, C_locale);
|
||
|
}
|
||
|
|
||
|
switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1))
|
||
|
{
|
||
|
case 0: // apply gain
|
||
|
return (float)pow(10.0f, dB / 20.0f);
|
||
|
case 1: // apply gain, but don't clip
|
||
|
return min((float)pow(10.0f, dB / 20.0f), 1.0f / peak);
|
||
|
case 2: // normalize
|
||
|
return 1.0f / peak;
|
||
|
case 3: // prevent clipping
|
||
|
if (peak > 1.0f)
|
||
|
return 1.0f / peak;
|
||
|
else
|
||
|
return 1.0f;
|
||
|
}
|
||
|
}
|
||
|
/* else
|
||
|
{
|
||
|
float dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0);
|
||
|
return pow(10.0f, dB / 20.0f);
|
||
|
}*/
|
||
|
}
|
||
|
|
||
|
return 1.0f; // no gain
|
||
|
}
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// Open
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
static char *jnl_strndup(const char *str, size_t n)
|
||
|
{
|
||
|
char *o = (char *)calloc(n+1, sizeof(char));
|
||
|
if (!o)
|
||
|
return 0;
|
||
|
|
||
|
strncpy(o, str, n);
|
||
|
o[n]=0;
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
static int parse_url(const char *url, char **prot, char **host, unsigned short *port, char **req, char **lp)
|
||
|
{
|
||
|
free(*prot); *prot=0;
|
||
|
free(*host); *host = 0;
|
||
|
free(*req); *req = 0;
|
||
|
free(*lp); *lp = 0;
|
||
|
*port = 0;
|
||
|
|
||
|
const char *p;
|
||
|
const char *protocol = strstr(url, "://");
|
||
|
if (protocol)
|
||
|
{
|
||
|
*prot = jnl_strndup(url, protocol-url);
|
||
|
p = protocol + 3;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p = url;
|
||
|
}
|
||
|
|
||
|
while (p && *p == '/') p++; // skip extra /
|
||
|
|
||
|
size_t end = strcspn(p, "@/");
|
||
|
|
||
|
// check for username
|
||
|
if (p[end] == '@')
|
||
|
{
|
||
|
*lp = jnl_strndup(p, end);
|
||
|
p = p+end+1;
|
||
|
end = strcspn(p, "[:/");
|
||
|
}
|
||
|
|
||
|
if (p[0] == '[') // IPv6 style address
|
||
|
{
|
||
|
p++;
|
||
|
const char *ipv6_end = strchr(p, ']');
|
||
|
if (!ipv6_end)
|
||
|
return 1;
|
||
|
|
||
|
*host = jnl_strndup(p, ipv6_end-p);
|
||
|
p = ipv6_end+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
end = strcspn(p, ":/");
|
||
|
*host = jnl_strndup(p, end);
|
||
|
p += end;
|
||
|
}
|
||
|
|
||
|
// is there a port number?
|
||
|
if (p[0] == ':')
|
||
|
{
|
||
|
char *new_end;
|
||
|
*port = (unsigned short)strtoul(p+1, &new_end, 10);
|
||
|
p = new_end;
|
||
|
}
|
||
|
|
||
|
if (p[0])
|
||
|
{
|
||
|
*req = _strdup(p);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void parseURL(const char *url, char **host, unsigned short *port, char **req, char **lp)
|
||
|
{
|
||
|
char *prot=0;
|
||
|
|
||
|
parse_url(url, &prot, host, port, req, lp);
|
||
|
if (!*port)
|
||
|
{
|
||
|
if (prot)
|
||
|
{
|
||
|
if (!stricmp(prot, "https"))
|
||
|
*port = 443;
|
||
|
else
|
||
|
*port = 80;
|
||
|
}
|
||
|
else
|
||
|
*port=80;
|
||
|
}
|
||
|
|
||
|
free(prot);
|
||
|
|
||
|
if (!*req)
|
||
|
*req = _strdup("/");
|
||
|
}
|
||
|
|
||
|
static void encodeMimeStr(char *in, char *out)
|
||
|
{
|
||
|
char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
int shift = 0;
|
||
|
int accum = 0;
|
||
|
|
||
|
while (in && *in)
|
||
|
{
|
||
|
if (*in)
|
||
|
{
|
||
|
accum <<= 8;
|
||
|
shift += 8;
|
||
|
accum |= *in++;
|
||
|
}
|
||
|
while (shift >= 6)
|
||
|
{
|
||
|
shift -= 6;
|
||
|
*out++ = alphabet[(accum >> shift) & 0x3F];
|
||
|
}
|
||
|
}
|
||
|
if (shift == 4)
|
||
|
{
|
||
|
*out++ = alphabet[(accum & 0xF) << 2];
|
||
|
*out++ = '=';
|
||
|
}
|
||
|
else if (shift == 2)
|
||
|
{
|
||
|
*out++ = alphabet[(accum & 0x3) << 4];
|
||
|
*out++ = '=';
|
||
|
*out++ = '=';
|
||
|
}
|
||
|
|
||
|
*out++ = 0;
|
||
|
}
|
||
|
|
||
|
int CGioFile::doConnect(const char *str, int start_offset)
|
||
|
{
|
||
|
char *http_ver_str = " HTTP/1.0\r\n";
|
||
|
|
||
|
unsigned short proxy_port = 80;
|
||
|
char str2[1024]={0};
|
||
|
|
||
|
if (!str)
|
||
|
str = last_full_url;
|
||
|
else
|
||
|
lstrcpynA( last_full_url, str, sizeof( last_full_url ) );
|
||
|
|
||
|
if (start_offset > 0)
|
||
|
{
|
||
|
http_ver_str = " HTTP/1.1\r\n";
|
||
|
}
|
||
|
|
||
|
lstrcpynA(str2, str, 1024);
|
||
|
|
||
|
is_stream_seek = start_offset || !str;
|
||
|
|
||
|
lstrcpynA(g_stream_title, str, 256);
|
||
|
|
||
|
meta_interval = meta_pos = 0;
|
||
|
server_name[0] = 0;
|
||
|
last_title_sent[0] = 0;
|
||
|
stream_bytes_read = start_offset;
|
||
|
stream_metabytes_read = 0;
|
||
|
m_is_stream = 1;
|
||
|
constate = 0;
|
||
|
parseURL(str2, &host, &port, &request, &lpinfo);
|
||
|
if (port == 80 || !GetPrivateProfileIntA("Winamp", "proxyonly80", 0, INI_FILE))
|
||
|
{
|
||
|
const char *p;
|
||
|
const char *winamp_proxy = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_PROXY_STRING);
|
||
|
if (!winamp_proxy || winamp_proxy == (char *)1)
|
||
|
{
|
||
|
char temp[256] = {0};
|
||
|
GetPrivateProfileStringA("Winamp", "proxy", "", temp, sizeof(temp), INI_FILE);
|
||
|
p = temp;
|
||
|
}
|
||
|
else
|
||
|
p = winamp_proxy;
|
||
|
|
||
|
while (p && (*p == ' ' || *p == '\t')) p++;
|
||
|
char config_proxy[512] = "http://";
|
||
|
StringCchCatA(config_proxy, 512, p);
|
||
|
parseURL(config_proxy, &proxy_host, &proxy_port, &req, &proxy_lp);
|
||
|
}
|
||
|
|
||
|
if (!host || !host[0])
|
||
|
return 1;
|
||
|
char *p = request + strlen(request);
|
||
|
while (p >= request && *p != '/') p--;
|
||
|
if (p[1])
|
||
|
lstrcpynA(g_stream_title, ++p, 256);
|
||
|
lstrcpynA(stream_title_save, g_stream_title, 580);
|
||
|
lstrcpynA(stream_name, g_stream_title, 256);
|
||
|
g_stream_title[255] = 0;
|
||
|
fEof = false;
|
||
|
timeout_start = GetTickCount();
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_CONNECTING,lastfn_status,256);
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
if (force_lpinfo[0])
|
||
|
{
|
||
|
free(lpinfo);
|
||
|
lpinfo = _strdup(force_lpinfo);
|
||
|
}
|
||
|
|
||
|
if (!m_dns) jnl_dns_create(&m_dns);
|
||
|
m_connection = CreateConnection(str, m_dns, 16384, recvbuffersize = max(32768, config_http_buffersize * 1024));
|
||
|
if (!m_connection)
|
||
|
return 1;
|
||
|
|
||
|
if (!proxy_host || !proxy_host[0])
|
||
|
{
|
||
|
jnl_connection_connect(m_connection, host, port);
|
||
|
jnl_connection_send_string(m_connection, "GET ");
|
||
|
jnl_connection_send_string(m_connection, request);
|
||
|
jnl_connection_send_string(m_connection, http_ver_str);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
char s[32]={0};
|
||
|
jnl_connection_connect(m_connection, proxy_host, proxy_port);
|
||
|
jnl_connection_send_string(m_connection, "GET http://");
|
||
|
if (lpinfo && lpinfo[0])
|
||
|
{
|
||
|
jnl_connection_send_string(m_connection, lpinfo);
|
||
|
jnl_connection_send_string(m_connection, "@");
|
||
|
}
|
||
|
jnl_connection_send_string(m_connection, host);
|
||
|
StringCchPrintfA(s, 32, ":%d", port);
|
||
|
jnl_connection_send_string(m_connection, s);
|
||
|
jnl_connection_send_string(m_connection, request);
|
||
|
jnl_connection_send_string(m_connection, http_ver_str);
|
||
|
if (proxy_lp && proxy_lp[0])
|
||
|
{
|
||
|
char temp[1024]={0};
|
||
|
jnl_connection_send_string(m_connection, "Proxy-Authorization: Basic ");
|
||
|
encodeMimeStr(proxy_lp, temp);
|
||
|
jnl_connection_send_string(m_connection, temp);
|
||
|
jnl_connection_send_string(m_connection, "\r\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
jnl_connection_send_string(m_connection, "Host: ");
|
||
|
jnl_connection_send_string(m_connection, host);
|
||
|
jnl_connection_send_string(m_connection, "\r\n");
|
||
|
|
||
|
if (!start_offset)
|
||
|
{
|
||
|
jnl_connection_send_string(m_connection, GetUltravoxUserAgent());
|
||
|
}
|
||
|
else
|
||
|
jnl_connection_send_string(m_connection, GetUserAgent());
|
||
|
|
||
|
jnl_connection_send_string(m_connection, "Accept: */*\r\n");
|
||
|
|
||
|
if (allow_sctitles && !start_offset) jnl_connection_send_string(m_connection, "Icy-MetaData:1\r\n");
|
||
|
|
||
|
if (lpinfo && lpinfo[0])
|
||
|
{
|
||
|
char str[512] = {0};
|
||
|
encodeMimeStr(lpinfo, str);
|
||
|
jnl_connection_send_string(m_connection, "Authorization: Basic ");
|
||
|
jnl_connection_send_string(m_connection, str);
|
||
|
jnl_connection_send_string(m_connection, "\r\n");
|
||
|
}
|
||
|
if (start_offset > 0)
|
||
|
{
|
||
|
char str[512] = {0};
|
||
|
StringCchPrintfA(str, 512, "Range: bytes=%d-\r\n", start_offset);
|
||
|
jnl_connection_send_string(m_connection, str);
|
||
|
}
|
||
|
jnl_connection_send_string(m_connection, "Connection: close\r\n");
|
||
|
jnl_connection_send_string(m_connection, "\r\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int CGioFile::Open(const wchar_t *pszName, int maxbufsizek)
|
||
|
{
|
||
|
peekBuffer.reserve(16384);
|
||
|
|
||
|
m_vbr_flag = 0;
|
||
|
m_vbr_frames = 0;
|
||
|
m_vbr_samples = 0;
|
||
|
m_vbr_ms = 0;
|
||
|
m_vbr_frame_len = 0;
|
||
|
|
||
|
m_id3v2_len = 0;
|
||
|
m_id3v1_len = 0;
|
||
|
m_apev2_len = 0;
|
||
|
|
||
|
lyrics3_size = 0;
|
||
|
|
||
|
m_is_stream = 0;
|
||
|
|
||
|
mpeg_length = 0;
|
||
|
mpeg_position = 0;
|
||
|
file_position = 0;
|
||
|
|
||
|
if ( !_wcsnicmp( pszName, L"file://", 7 ) )
|
||
|
pszName += 7;
|
||
|
|
||
|
if ( PathIsURL( pszName ) )
|
||
|
{
|
||
|
wchar_t str[ 8192 ] = { 0 };
|
||
|
wchar_t *p;
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
lstrcpyn( str, pszName, 8192 );
|
||
|
save_filename[ 0 ] = 0;
|
||
|
if ( p = wcsstr( str, L">save.file:" ) )
|
||
|
{
|
||
|
*p = 0;
|
||
|
p += 11;
|
||
|
if ( !wcsstr( p, L".." ) && !wcsstr( p, L"\\" ) && !wcsstr( p, L"/" ) )
|
||
|
{
|
||
|
lstrcpynA( save_filename, AutoChar( p ), 256 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( doConnect( AutoChar( str ), 0 ) )
|
||
|
return NErr_ConnectionFailed;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hFile = CreateFile(pszName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
|
||
|
|
||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
GetFileTime( hFile, 0, 0, &last_write_time );
|
||
|
mpeg_length = Seek64( hFile, 0, FILE_END );
|
||
|
|
||
|
uint64_t startOffset = 0;
|
||
|
unsigned char buf[ 1448 ] = { 0 };
|
||
|
|
||
|
DWORD len = 0;
|
||
|
while ( 1 ) // read all tags (sometimes programs get stupid and make multiple tags)
|
||
|
{
|
||
|
len = 0;
|
||
|
Seek64( hFile, startOffset, FILE_BEGIN );
|
||
|
ReadFile( hFile, buf, 10, &len, NULL );
|
||
|
|
||
|
if ( len >= 10 &&
|
||
|
buf[ 0 ] == 'I' &&
|
||
|
buf[ 1 ] == 'D' &&
|
||
|
buf[ 2 ] == '3' &&
|
||
|
buf[ 3 ] != 255 &&
|
||
|
buf[ 4 ] != 255 &&
|
||
|
buf[ 6 ] < 0x80 &&
|
||
|
buf[ 7 ] < 0x80 &&
|
||
|
buf[ 8 ] < 0x80 &&
|
||
|
buf[ 9 ] < 0x80 )
|
||
|
{
|
||
|
DWORD thisLen = 10;
|
||
|
if ( buf[ 5 ] & 0x10 ) // check for footer flag
|
||
|
thisLen += 10;
|
||
|
|
||
|
thisLen += ( (int)buf[ 6 ] ) << 21;
|
||
|
thisLen += ( (int)buf[ 7 ] ) << 14;
|
||
|
thisLen += ( (int)buf[ 8 ] ) << 7;
|
||
|
thisLen += ( (int)buf[ 9 ] );
|
||
|
|
||
|
SetFilePointer( hFile, -10, NULL, FILE_CURRENT );
|
||
|
|
||
|
if ( stream_id3v2_buf ) // already read a tag?
|
||
|
{
|
||
|
startOffset += thisLen;
|
||
|
m_id3v2_len += thisLen;
|
||
|
mpeg_length -= thisLen;
|
||
|
mpeg_position += thisLen;
|
||
|
|
||
|
SetFilePointer( hFile, thisLen, NULL, FILE_CURRENT );
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
stream_id3v2_buf = (char *)malloc( thisLen );
|
||
|
if ( stream_id3v2_buf )
|
||
|
{
|
||
|
memcpy( stream_id3v2_buf, buf, 10 );
|
||
|
|
||
|
DWORD dummy = 0;
|
||
|
if ( !ReadFile( hFile, stream_id3v2_buf, thisLen, &dummy, 0 ) || dummy < thisLen )
|
||
|
{
|
||
|
free( stream_id3v2_buf );
|
||
|
stream_id3v2_buf = NULL;
|
||
|
|
||
|
thisLen = 0;
|
||
|
SetFilePointer( hFile, 0, NULL, FILE_END );
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mpeg_position += thisLen;
|
||
|
stream_id3v2_read = thisLen;
|
||
|
mpeg_length -= thisLen;
|
||
|
startOffset += thisLen;
|
||
|
|
||
|
info.Decode( stream_id3v2_buf, thisLen );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* memory allocation failed, let's assume the ID3v2 tag was valid ... */
|
||
|
mpeg_position += thisLen;
|
||
|
stream_id3v2_read = thisLen;
|
||
|
mpeg_length -= thisLen;
|
||
|
startOffset += thisLen;
|
||
|
}
|
||
|
|
||
|
m_id3v2_len += thisLen;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// benski> unnecessary because we call SetFilePointer immediately after the loop:
|
||
|
// CUT: SetFilePointer(hFile, -10, NULL, FILE_CURRENT);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Read ID3v1 Tag */
|
||
|
if ( mpeg_length >= 128 )
|
||
|
{
|
||
|
SetFilePointer( hFile, -128, NULL, FILE_END );
|
||
|
|
||
|
len = 0;
|
||
|
if ( ReadFile( hFile, id3v1_data, 128, &len, NULL ) && len == 128 )
|
||
|
{
|
||
|
if ( id3v1_data[ 0 ] == 'T' && id3v1_data[ 1 ] == 'A' && id3v1_data[ 2 ] == 'G' )
|
||
|
{
|
||
|
m_id3v1_len = 128;
|
||
|
mpeg_length -= m_id3v1_len;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* read appended ID3v2.4 tag */
|
||
|
if ( mpeg_length >= 10 && Seek64( hFile, mpeg_position + mpeg_length - 10, FILE_BEGIN ) != -1 )
|
||
|
{
|
||
|
len = 0;
|
||
|
ReadFile( hFile, buf, 10, &len, NULL );
|
||
|
if ( len >= 10 &&
|
||
|
buf[ 0 ] == '3' &&
|
||
|
buf[ 1 ] == 'D' &&
|
||
|
buf[ 2 ] == 'I' &&
|
||
|
buf[ 3 ] != 255 &&
|
||
|
buf[ 4 ] != 255 &&
|
||
|
buf[ 6 ] < 0x80 &&
|
||
|
buf[ 7 ] < 0x80 &&
|
||
|
buf[ 8 ] < 0x80 &&
|
||
|
buf[ 9 ] < 0x80 )
|
||
|
{
|
||
|
DWORD thisLen = 10;
|
||
|
if ( buf[ 5 ] & 0x10 ) // check for header flag
|
||
|
thisLen += 10;
|
||
|
|
||
|
thisLen += ( (int)buf[ 6 ] ) << 21;
|
||
|
thisLen += ( (int)buf[ 7 ] ) << 14;
|
||
|
thisLen += ( (int)buf[ 8 ] ) << 7;
|
||
|
thisLen += ( (int)buf[ 9 ] );
|
||
|
|
||
|
mpeg_length -= thisLen;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Read Lyrics3 Tag */
|
||
|
if ( mpeg_length >= 15 )
|
||
|
{
|
||
|
free( lyrics3_data );
|
||
|
lyrics3_data = NULL;
|
||
|
|
||
|
lyrics3_size = 0;
|
||
|
char lyrics3_end_signature[ 15 ] = { 0 };
|
||
|
|
||
|
Seek64( hFile, ( mpeg_position + mpeg_length - 15 ), FILE_BEGIN );
|
||
|
|
||
|
len = 0;
|
||
|
if ( ReadFile( hFile, lyrics3_end_signature, 15, &len, NULL ) && len == 15 )
|
||
|
{
|
||
|
if ( !memcmp( lyrics3_end_signature + 6, "LYRICS200", 9 ) )
|
||
|
{
|
||
|
lyrics3_size = strtoul( lyrics3_end_signature, 0, 10 );
|
||
|
if ( lyrics3_size )
|
||
|
{
|
||
|
lyrics3_data = (char *)malloc( lyrics3_size );
|
||
|
if ( lyrics3_data )
|
||
|
{
|
||
|
SetFilePointer( hFile, -(LONG)( 15 + lyrics3_size ), NULL, FILE_CURRENT );
|
||
|
|
||
|
len = 0;
|
||
|
ReadFile( hFile, lyrics3_data, lyrics3_size, &len, 0 );
|
||
|
if ( len != lyrics3_size || memcmp( lyrics3_data, "LYRICSBEGIN", 11 ) )
|
||
|
{
|
||
|
free( lyrics3_data );
|
||
|
lyrics3_data = NULL;
|
||
|
|
||
|
lyrics3_size = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mpeg_length -= lyrics3_size + 15;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( mpeg_length >= 32 )
|
||
|
{
|
||
|
/* Read APEv2 Tag */
|
||
|
free( apev2_data );
|
||
|
apev2_data = NULL;
|
||
|
|
||
|
char ape[ 32 ] = { 0 };
|
||
|
Seek64( hFile, ( mpeg_position + mpeg_length - 32 ), FILE_BEGIN );
|
||
|
|
||
|
len = 0;
|
||
|
if ( ReadFile( hFile, ape, 32, &len, NULL ) && len == 32 )
|
||
|
{
|
||
|
APEv2::Header footer( ape );
|
||
|
if ( footer.Valid() )
|
||
|
{
|
||
|
m_apev2_len = footer.TagSize();
|
||
|
if ( mpeg_length >= m_apev2_len )
|
||
|
{
|
||
|
Seek64( hFile, -(int64_t)( m_apev2_len ), FILE_CURRENT );
|
||
|
|
||
|
apev2_data = (char *)malloc( m_apev2_len );
|
||
|
if ( apev2_data )
|
||
|
{
|
||
|
len = 0;
|
||
|
ReadFile( hFile, apev2_data, m_apev2_len, &len, NULL );
|
||
|
if ( len != m_apev2_len || apev2.Parse( apev2_data, m_apev2_len ) != APEv2::APEV2_SUCCESS )
|
||
|
{
|
||
|
free( apev2_data );
|
||
|
apev2_data = NULL;
|
||
|
|
||
|
m_apev2_len = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mpeg_length -= m_apev2_len;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
{
|
||
|
Seek64( hFile, mpeg_position, FILE_BEGIN );
|
||
|
|
||
|
len = 0;
|
||
|
ReadFile( hFile, buf, sizeof( buf ), &len, NULL );
|
||
|
|
||
|
delete m_vbr_hdr;
|
||
|
m_vbr_hdr = NULL;
|
||
|
|
||
|
LAMEinfo lame;
|
||
|
lame.toc = m_vbr_toc;
|
||
|
|
||
|
m_vbr_frame_len = ReadLAMEinfo( buf, &lame );
|
||
|
if ( m_vbr_frame_len )
|
||
|
{
|
||
|
lengthVerified = false;
|
||
|
prepad = lame.encoderDelay;
|
||
|
postpad = lame.padding;
|
||
|
|
||
|
encodingMethod = lame.encodingMethod;
|
||
|
if ( !encodingMethod && lame.cbr )
|
||
|
encodingMethod = ENCODING_METHOD_CBR;
|
||
|
|
||
|
if ( lame.flags & TOC_FLAG )
|
||
|
{
|
||
|
int x;
|
||
|
for ( x = 0; x < 100; x++ )
|
||
|
if ( m_vbr_toc[ x ] ) break;
|
||
|
|
||
|
if ( x != 100 )
|
||
|
m_vbr_flag = 1;
|
||
|
}
|
||
|
|
||
|
if ( lame.flags & BYTES_FLAG )
|
||
|
{
|
||
|
// some programs are stupid and count the id3v2 length in the lame header
|
||
|
if ( mpeg_length + m_id3v2_len == lame.bytes || mpeg_length + m_id3v2_len + m_id3v1_len == lame.bytes )
|
||
|
{
|
||
|
m_vbr_bytes = mpeg_length;
|
||
|
lengthVerified = true;
|
||
|
}
|
||
|
else if ( abs( (int)mpeg_length - lame.bytes ) < MAX_ACCEPTABLE_DEVIANCE )
|
||
|
{
|
||
|
m_vbr_bytes = lame.bytes;
|
||
|
lengthVerified = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( lame.flags & FRAMES_FLAG
|
||
|
&& m_vbr_bytes && lengthVerified ) // only use the length if we're sure it's unmodified
|
||
|
{
|
||
|
m_vbr_frames = lame.frames;
|
||
|
m_vbr_samples = Int32x32To64( lame.frames, lame.h_id ? 1152 : 576 );
|
||
|
m_vbr_samples -= ( prepad + postpad );
|
||
|
m_vbr_ms = MulDiv( (int)m_vbr_samples, 1000, lame.samprate );
|
||
|
}
|
||
|
|
||
|
if ( !m_vbr_frames || encodingMethod == ENCODING_METHOD_CBR )
|
||
|
m_vbr_flag = 0;
|
||
|
|
||
|
mpeg_length -= m_vbr_frame_len;
|
||
|
mpeg_position += m_vbr_frame_len;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vbr_hdr = new CVbriHeader;
|
||
|
|
||
|
m_vbr_frame_len = m_vbr_hdr->readVbriHeader(buf);
|
||
|
if (m_vbr_frame_len)
|
||
|
{
|
||
|
m_vbr_bytes = m_vbr_hdr->getBytes();
|
||
|
m_vbr_frames = m_vbr_hdr->getNumFrames();
|
||
|
m_vbr_ms = m_vbr_hdr->getNumMS();
|
||
|
|
||
|
lengthVerified = true;
|
||
|
|
||
|
mpeg_length -= m_vbr_frame_len;
|
||
|
mpeg_position += m_vbr_frame_len;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete m_vbr_hdr;
|
||
|
m_vbr_hdr = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// read OFL
|
||
|
{
|
||
|
Seek64( hFile, mpeg_position, FILE_BEGIN );
|
||
|
len = 0;
|
||
|
ReadFile( hFile, buf, sizeof( buf ), &len, NULL );
|
||
|
MPEGFrame frame;
|
||
|
frame.ReadBuffer( buf );
|
||
|
OFL ofl;
|
||
|
if ( ofl.Read( frame, &buf[ 4 ], len - 4 ) == NErr_Success )
|
||
|
{
|
||
|
m_vbr_ms = (int)ofl.GetLengthSeconds() * 1000;
|
||
|
m_vbr_samples = ofl.GetSamples();
|
||
|
|
||
|
m_vbr_frames = ofl.GetFrames();
|
||
|
size_t pre, post;
|
||
|
if ( ofl.GetGaps( &pre, &post ) == NErr_Success )
|
||
|
{
|
||
|
prepad = (int)pre - 529;
|
||
|
postpad = (int)post + 529;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ReadiTunesGaps();
|
||
|
|
||
|
Seek64( hFile, mpeg_position, FILE_BEGIN );
|
||
|
|
||
|
if ( maxbufsizek * 1024 >= mpeg_length )
|
||
|
{
|
||
|
DWORD m_full_buffer_len = 0;
|
||
|
m_full_buffer = new unsigned char[ (unsigned int)mpeg_length ];
|
||
|
if ( !ReadFile( hFile, m_full_buffer, (DWORD)mpeg_length, &m_full_buffer_len, NULL ) )
|
||
|
{
|
||
|
CloseHandle( hFile );
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
delete[] m_full_buffer;
|
||
|
m_full_buffer = NULL;
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
CloseHandle( hFile );
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
m_full_buffer_pos = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_full_buffer = NULL;
|
||
|
}
|
||
|
|
||
|
fEof = false;
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//DWORD dwError = GetLastError();
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// Close
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
int CGioFile::Close()
|
||
|
{
|
||
|
int dwReturn = NErr_Success;
|
||
|
|
||
|
if (m_is_stream)
|
||
|
{
|
||
|
jnl_connection_release(m_connection);
|
||
|
if (m_dns) jnl_dns_release(m_dns);
|
||
|
m_dns = 0;
|
||
|
if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
m_is_stream = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete [] m_full_buffer;
|
||
|
m_full_buffer = NULL;
|
||
|
|
||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
dwReturn = CloseHandle(hFile) ? NErr_Success : NErr_Error;
|
||
|
hFile = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dwReturn;
|
||
|
}
|
||
|
|
||
|
void CGioFile::processMetaData(char *data, int len, int msgId)
|
||
|
{
|
||
|
if (len && *data)
|
||
|
{
|
||
|
char *ld = NULL;
|
||
|
int x;
|
||
|
if (len > 4096) return ;
|
||
|
for (x = 0; x < len; x ++)
|
||
|
if (!data[x]) break;
|
||
|
if (x == len) return ;
|
||
|
while ((ld = strstr(data, "='")))
|
||
|
{
|
||
|
char * n = data;
|
||
|
ld[0] = 0;
|
||
|
ld += 2;
|
||
|
data = strstr(ld, "';");
|
||
|
if (data)
|
||
|
{
|
||
|
data[0] = 0;
|
||
|
data += 2;
|
||
|
if (!_stricmp(n, "StreamTitle"))
|
||
|
{
|
||
|
lstrcpynA(g_stream_title, ld, sizeof(g_stream_title));
|
||
|
lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent));
|
||
|
lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title));
|
||
|
if (sctitle_format)
|
||
|
{
|
||
|
StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : "");
|
||
|
StringCchCatA(g_stream_title, 256, stream_title_save);
|
||
|
StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : "");
|
||
|
}
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
else if (!_stricmp(n, "StreamUrl"))
|
||
|
{
|
||
|
lstrcpynA(stream_url, ld, sizeof(stream_url));
|
||
|
DWORD_PTR dw;
|
||
|
if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw);
|
||
|
}
|
||
|
}
|
||
|
else break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
INT_PTR CALLBACK CGioFile::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
CGioFile *_this;
|
||
|
switch (uMsg)
|
||
|
{
|
||
|
case WM_INITDIALOG:
|
||
|
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
|
||
|
_this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
|
||
|
if (_this->force_lpinfo[0])
|
||
|
SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo);
|
||
|
else SetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->lpinfo?_this->lpinfo:"");
|
||
|
SetDlgItemTextA(hwndDlg, IDC_REALM, _this->dlg_realm);
|
||
|
return 1;
|
||
|
case WM_COMMAND:
|
||
|
_this = (CGioFile *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
|
||
|
if (LOWORD(wParam) == IDOK)
|
||
|
{
|
||
|
GetDlgItemTextA(hwndDlg, IDC_EDIT1, _this->force_lpinfo, sizeof(_this->force_lpinfo));
|
||
|
EndDialog(hwndDlg, 1);
|
||
|
}
|
||
|
else if (LOWORD(wParam) == IDCANCEL)
|
||
|
{
|
||
|
EndDialog(hwndDlg, 0);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
class GioFileFiller : public Filler
|
||
|
{
|
||
|
public:
|
||
|
GioFileFiller(CGioFile *_file)
|
||
|
{
|
||
|
ret=NErr_Success;
|
||
|
file=_file;
|
||
|
}
|
||
|
|
||
|
size_t Read(void *dest, size_t len)
|
||
|
{
|
||
|
int bytesRead=0;
|
||
|
ret = file->Read(dest, (int)len, &bytesRead);
|
||
|
return bytesRead;
|
||
|
}
|
||
|
|
||
|
CGioFile *file;
|
||
|
int ret;
|
||
|
};
|
||
|
|
||
|
int CGioFile::Peek(void *pBuffer, int cbToRead, int *pcbRead)
|
||
|
{
|
||
|
GioFileFiller filler(this);
|
||
|
|
||
|
// do we need to fill up?
|
||
|
if (cbToRead > (int)peekBuffer.size())
|
||
|
{
|
||
|
no_peek_hack = true;// benski> HACK ALERT! we have to set this or else Read() will try to use the peek buffer
|
||
|
peekBuffer.fill(&filler, cbToRead - peekBuffer.size());
|
||
|
no_peek_hack=false;
|
||
|
}
|
||
|
|
||
|
*pcbRead = (int)peekBuffer.peek(pBuffer, cbToRead);
|
||
|
return filler.ret;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// Read
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
int CGioFile::Read(void *pBuffer, int cbToRead, int *pcbRead)
|
||
|
{
|
||
|
TITLELISTTYPE *mylist = TitleLinkedList;
|
||
|
// these are used for SHOUTCAST2 metadata and artwork since it can provide
|
||
|
// multi-packet metadata chunks which are out of order or are received in
|
||
|
// a way which would otherwise cause these to be cleared incorrectly
|
||
|
static int title_parts = 0, stream_art_parts = 0, stream_art_parts_total_len = 0,
|
||
|
playing_art_parts = 0, playing_art_parts_total_len = 0;
|
||
|
static char **title_blocks = 0, **stream_art_blocks = 0, **playing_art_blocks = 0;
|
||
|
|
||
|
if (mylist != &TitleListTerminator && mod.outMod)
|
||
|
{
|
||
|
while (mylist->Next && mylist != &TitleListTerminator)
|
||
|
{
|
||
|
TITLELISTTYPE *next = (TITLELISTTYPE *)mylist->Next;
|
||
|
long now = mod.outMod->GetOutputTime();
|
||
|
|
||
|
if (mylist->title[0] || mylist->part_len)
|
||
|
{
|
||
|
if (!mylist->timer || now >= mylist->timer)
|
||
|
{
|
||
|
switch(mylist->style)
|
||
|
{
|
||
|
case UVOX_METADATA_STYLE_AOLRADIO:
|
||
|
{
|
||
|
EnterCriticalSection(&streamInfoLock);
|
||
|
free(uvox_3901);
|
||
|
uvox_3901 = _strdup(mylist->title);
|
||
|
LeaveCriticalSection(&streamInfoLock);
|
||
|
if (g_playing_file)
|
||
|
{
|
||
|
PostMessage(mod.hMainWindow, WM_USER, (WPARAM) "0x3901", IPC_METADATA_CHANGED);
|
||
|
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case UVOX_METADATA_STYLE_SHOUTCAST:
|
||
|
{
|
||
|
processMetaData(mylist->title, (int)strlen(mylist->title) + 1);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case UVOX_METADATA_STYLE_SHOUTCAST2:
|
||
|
{
|
||
|
EnterCriticalSection(&streamInfoLock);
|
||
|
// so we can recombine multi-packets we'll store things and
|
||
|
// when all of the packets have been read then we form them
|
||
|
if(!title_blocks)
|
||
|
title_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts));
|
||
|
|
||
|
title_blocks[mylist->part-1] = _strdup(mylist->title);
|
||
|
title_parts++;
|
||
|
|
||
|
// sanity check so we only try to get the metadata if all parts were processed
|
||
|
if (title_parts == mylist->total_parts)
|
||
|
{
|
||
|
free(uvox_3902);
|
||
|
uvox_3902 = (char *)malloc(16377 * mylist->total_parts);
|
||
|
uvox_3902[0] = 0;
|
||
|
title_parts = 0;
|
||
|
|
||
|
for(int i = 0; i < mylist->total_parts; i++)
|
||
|
{
|
||
|
StringCchCatA(uvox_3902, mylist->total_parts * 16384, title_blocks[i]);
|
||
|
free(title_blocks[i]);
|
||
|
}
|
||
|
free(title_blocks);
|
||
|
title_blocks = 0;
|
||
|
|
||
|
// attempt to form a title as 'artist - album - title' as sc_serv2 feeds
|
||
|
// changed for 5.61 to be just 'artist - title' to match v1 and v2 tools
|
||
|
Ultravox3902 uvox_metadata;
|
||
|
if (uvox_metadata.Parse(uvox_3902) != API_XML_FAILURE)
|
||
|
{
|
||
|
wchar_t stream_title[256] = {0};
|
||
|
char* fields[] = {"artist", "title"};
|
||
|
for(int i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
|
||
|
{
|
||
|
wchar_t temp[256] = {0};
|
||
|
int ret = uvox_metadata.GetExtendedData(fields[i], temp, 256);
|
||
|
if(ret && temp[0])
|
||
|
{
|
||
|
if(stream_title[0]) StringCchCatW(stream_title, 256, L" - ");
|
||
|
StringCchCatW(stream_title, 256, temp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lstrcpynA(g_stream_title, AutoChar(stream_title, CP_UTF8), sizeof(g_stream_title));
|
||
|
lstrcpynA(last_title_sent, g_stream_title, sizeof(last_title_sent));
|
||
|
lstrcpynA(stream_current_title, g_stream_title, sizeof(stream_current_title));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_stream_title[0] = 0;
|
||
|
last_title_sent[0] = 0;
|
||
|
}
|
||
|
|
||
|
if (sctitle_format)
|
||
|
{
|
||
|
StringCchCatA(g_stream_title, 256, *stream_title_save ? *g_stream_title ? " (" : "(" : "");
|
||
|
StringCchCatA(g_stream_title, 256, stream_title_save);
|
||
|
StringCchCatA(g_stream_title, 256, *stream_title_save ? ")" : "");
|
||
|
}
|
||
|
LeaveCriticalSection(&streamInfoLock);
|
||
|
|
||
|
if (g_playing_file)
|
||
|
{
|
||
|
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK:
|
||
|
{
|
||
|
EnterCriticalSection(&streamInfoLock);
|
||
|
// so we can recombine multi-packets we'll store things and
|
||
|
// when all of the packets have been read then we form them
|
||
|
if(!stream_art_blocks)
|
||
|
{
|
||
|
stream_art_parts_total_len = 0;
|
||
|
stream_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts));
|
||
|
}
|
||
|
|
||
|
stream_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1);
|
||
|
memcpy(stream_art_blocks[mylist->part-1], mylist->title, mylist->part_len);
|
||
|
stream_art_parts++;
|
||
|
stream_art_parts_total_len += mylist->part_len;
|
||
|
|
||
|
// sanity check so we only try to get the metadata if all parts were processed
|
||
|
if (stream_art_parts == mylist->total_parts)
|
||
|
{
|
||
|
free(uvox_artwork.uvox_stream_artwork);
|
||
|
if (stream_art_parts_total_len <= 0) break;
|
||
|
uvox_artwork.uvox_stream_artwork = (char *)malloc(stream_art_parts_total_len);
|
||
|
uvox_artwork.uvox_stream_artwork[0] = 0;
|
||
|
uvox_artwork.uvox_stream_artwork_len = stream_art_parts_total_len;
|
||
|
uvox_artwork.uvox_stream_artwork_type = mylist->type;
|
||
|
stream_art_parts = 0;
|
||
|
|
||
|
char *art = uvox_artwork.uvox_stream_artwork;
|
||
|
for(int i = 0; i < mylist->total_parts; i++)
|
||
|
{
|
||
|
int size = min(stream_art_parts_total_len, 16371);
|
||
|
if (size > 0 && size <= 16371)
|
||
|
{
|
||
|
memcpy(art, stream_art_blocks[i], size);
|
||
|
stream_art_parts_total_len -= size;
|
||
|
art += size;
|
||
|
}
|
||
|
free(stream_art_blocks[i]);
|
||
|
}
|
||
|
free(stream_art_blocks);
|
||
|
stream_art_blocks = 0;
|
||
|
}
|
||
|
LeaveCriticalSection(&streamInfoLock);
|
||
|
|
||
|
if (g_playing_file)
|
||
|
{
|
||
|
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING:
|
||
|
{
|
||
|
EnterCriticalSection(&streamInfoLock);
|
||
|
// so we can recombine multi-packets we'll store things and
|
||
|
// when all of the packets have been read then we form them
|
||
|
if(!playing_art_blocks)
|
||
|
{
|
||
|
playing_art_parts_total_len = 0;
|
||
|
playing_art_blocks = (char **)malloc(sizeof(char*)*(mylist->total_parts));
|
||
|
}
|
||
|
|
||
|
playing_art_blocks[mylist->part-1] = (char *)malloc(mylist->part_len+1);
|
||
|
memcpy(playing_art_blocks[mylist->part-1], mylist->title, mylist->part_len);
|
||
|
playing_art_parts++;
|
||
|
playing_art_parts_total_len += mylist->part_len;
|
||
|
|
||
|
// sanity check so we only try to get the metadata if all parts were processed
|
||
|
if (playing_art_parts == mylist->total_parts)
|
||
|
{
|
||
|
free(uvox_artwork.uvox_playing_artwork);
|
||
|
if (playing_art_parts_total_len <= 0) break;
|
||
|
uvox_artwork.uvox_playing_artwork = (char *)malloc(playing_art_parts_total_len);
|
||
|
uvox_artwork.uvox_playing_artwork[0] = 0;
|
||
|
uvox_artwork.uvox_playing_artwork_len = playing_art_parts_total_len;
|
||
|
uvox_artwork.uvox_playing_artwork_type = mylist->type;
|
||
|
|
||
|
playing_art_parts = 0;
|
||
|
|
||
|
char *art = uvox_artwork.uvox_playing_artwork;
|
||
|
for(int i = 0; i < mylist->total_parts; i++)
|
||
|
{
|
||
|
int size = min(playing_art_parts_total_len, 16371);
|
||
|
if (size > 0 && size <= 16371)
|
||
|
{
|
||
|
memcpy(art, playing_art_blocks[i], size);
|
||
|
playing_art_parts_total_len -= size;
|
||
|
art += size;
|
||
|
}
|
||
|
free(playing_art_blocks[i]);
|
||
|
}
|
||
|
free(playing_art_blocks);
|
||
|
playing_art_blocks = 0;
|
||
|
}
|
||
|
LeaveCriticalSection(&streamInfoLock);
|
||
|
|
||
|
if (g_playing_file)
|
||
|
{
|
||
|
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
removeTitleListEntry(mylist);
|
||
|
}
|
||
|
}
|
||
|
mylist = next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!no_peek_hack && peekBuffer.size())
|
||
|
{
|
||
|
*pcbRead = (int)peekBuffer.read(pBuffer, cbToRead);
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
|
||
|
BOOL bSuccess;
|
||
|
if (m_is_stream)
|
||
|
{
|
||
|
if (m_connection)
|
||
|
{
|
||
|
char str[4096]={0};
|
||
|
if (pcbRead) *pcbRead = 0;
|
||
|
jnl_connection_run(m_connection, -1, -1, 0, 0);
|
||
|
if (constate == 0)
|
||
|
{
|
||
|
if ( jnl_connection_receive_lines_available( m_connection ) > 0 )
|
||
|
{
|
||
|
char *p = str;
|
||
|
jnl_connection_receive_line( m_connection, str, 4096 );
|
||
|
|
||
|
// check http version and type of data, partial or full
|
||
|
if ( strlen( str ) >= 13 && !_strnicmp( str, "HTTP/1.1", 8 ) )
|
||
|
{
|
||
|
char *p = str + 8; while ( p && *p == ' ' ) p++;
|
||
|
m_http_response = ( p ? atoi( p ) : 0 );
|
||
|
if ( m_http_response == 200 )
|
||
|
m_seek_reset = true;
|
||
|
}
|
||
|
|
||
|
EnterCriticalSection( &g_lfnscs );
|
||
|
lstrcpynA( lastfn_status, str, 256 );
|
||
|
LeaveCriticalSection( &g_lfnscs );
|
||
|
PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE );
|
||
|
while ( p && *p && *p != ' ' ) p++;
|
||
|
if ( p && *p ) p++;
|
||
|
|
||
|
if ( p[ 0 ] == '2' ) constate = 1;
|
||
|
else if ( strstr( p, "301" ) == p || strstr( p, "302" ) == p ||
|
||
|
strstr( p, "303" ) == p || strstr( p, "307" ) == p )
|
||
|
{
|
||
|
constate = 69;
|
||
|
}
|
||
|
else if ( strstr( p, "401" ) == p && m_auth_tries++ < 3 )
|
||
|
{
|
||
|
constate = 75;
|
||
|
}
|
||
|
else if ( strstr( p, "403" ) == p )
|
||
|
{
|
||
|
EnterCriticalSection( &g_lfnscs );
|
||
|
lstrcpynA( lastfn_status, "access denied", 256 );
|
||
|
lastfn_status_err = 1;
|
||
|
LeaveCriticalSection( &g_lfnscs );
|
||
|
jnl_connection_close( m_connection, 1 );
|
||
|
}
|
||
|
else if ( strstr( p, "503" ) == p )
|
||
|
{
|
||
|
EnterCriticalSection( &g_lfnscs );
|
||
|
lstrcpynA( lastfn_status, "server full", 256 );
|
||
|
lastfn_status_err = 1;
|
||
|
LeaveCriticalSection( &g_lfnscs );
|
||
|
jnl_connection_close( m_connection, 1 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lastfn_status_err = 1;
|
||
|
jnl_connection_close( m_connection, 1 );
|
||
|
}
|
||
|
}
|
||
|
else if ( jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_CLOSED || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_ERROR || jnl_connection_get_state( m_connection ) == JNL_CONNECTION_STATE_NOCONNECTION )
|
||
|
{
|
||
|
const char *t = jnl_connection_get_error( m_connection );
|
||
|
if ( t && *t )
|
||
|
{
|
||
|
EnterCriticalSection( &g_lfnscs );
|
||
|
lstrcpynA( lastfn_status, t, 256 );
|
||
|
lastfn_status_err = 1;
|
||
|
LeaveCriticalSection( &g_lfnscs );
|
||
|
}
|
||
|
PostMessage( mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE );
|
||
|
}
|
||
|
}
|
||
|
if (constate == 75) // authorization required
|
||
|
{
|
||
|
while (jnl_connection_receive_lines_available(m_connection) > 0)
|
||
|
{
|
||
|
char *wwwa = "WWW-Authenticate:";
|
||
|
jnl_connection_receive_line(m_connection, str, 4096);
|
||
|
if (!str[0])
|
||
|
{
|
||
|
lastfn_status_err = 1; jnl_connection_close(m_connection, 1); break;
|
||
|
}
|
||
|
if (!_strnicmp(str, wwwa, strlen(wwwa)))
|
||
|
{
|
||
|
int has = 0;
|
||
|
char *s2 = "Basic realm=\"";
|
||
|
char *p = str + strlen(wwwa); while (p && *p == ' ') p++;
|
||
|
if (!_strnicmp(p, s2, strlen(s2)))
|
||
|
{
|
||
|
p += strlen(s2);
|
||
|
if (strstr(p, "\""))
|
||
|
{
|
||
|
if (p && *p)
|
||
|
{
|
||
|
strstr(p, "\"")[0] = 0;
|
||
|
extern char *get_inifile();
|
||
|
if (!force_lpinfo[0]) GetPrivateProfileStringA("HTTP-AUTH", p, "", force_lpinfo, sizeof(force_lpinfo), get_inifile());
|
||
|
if (!force_lpinfo[0] || (lpinfo && lpinfo[0]))
|
||
|
{
|
||
|
lstrcpynA(dlg_realm, p, sizeof(dlg_realm));
|
||
|
if (!WASABI_API_DIALOGBOXPARAM(IDD_HTTPAUTH, GetDialogBoxParent(), httpDlgProc, (LPARAM)this))
|
||
|
{
|
||
|
force_lpinfo[0] = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WritePrivateProfileStringA("HTTP-AUTH", p, force_lpinfo, get_inifile());
|
||
|
}
|
||
|
}
|
||
|
if (force_lpinfo[0])
|
||
|
{
|
||
|
jnl_connection_release(m_connection);
|
||
|
m_connection = NULL;
|
||
|
doConnect(NULL, 0);
|
||
|
has = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!has)
|
||
|
{
|
||
|
lastfn_status_err = 1;
|
||
|
jnl_connection_close(m_connection, 1);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (constate == 69) // redirect city
|
||
|
{
|
||
|
while (jnl_connection_receive_lines_available(m_connection) > 0)
|
||
|
{
|
||
|
jnl_connection_receive_line(m_connection, str, 4096);
|
||
|
if (!str[0])
|
||
|
{
|
||
|
jnl_connection_close(m_connection, 1); break;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Location:", 9))
|
||
|
{
|
||
|
char *p = str + 9; while (p && *p == ' ') p++;
|
||
|
if (p && *p)
|
||
|
{
|
||
|
if (m_redircnt++ < MAX_REDIRECTS)
|
||
|
{
|
||
|
jnl_connection_release(m_connection);
|
||
|
m_connection = NULL;
|
||
|
doConnect(p, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_REDIRECT_LIMIT_EXCEEDED,lastfn_status,256);
|
||
|
lastfn_status_err = 1;
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
jnl_connection_close(m_connection, 1);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (constate == 1)
|
||
|
{
|
||
|
while (jnl_connection_receive_lines_available(m_connection) > 0)
|
||
|
{
|
||
|
jnl_connection_receive_line(m_connection, str, 4096);
|
||
|
|
||
|
if (!str[0])
|
||
|
{
|
||
|
if (config_http_save_dir[0] && (config_miscopts&16))
|
||
|
{
|
||
|
if (!save_filename[0] && m_is_stream == 1 &&
|
||
|
strlen(stream_title_save) > 4 &&
|
||
|
!strstr(stream_title_save, "..") &&
|
||
|
!strstr(stream_title_save, "\\") &&
|
||
|
!strstr(stream_title_save, "/"))
|
||
|
{
|
||
|
lstrcpynA(save_filename, stream_title_save, 251);
|
||
|
char *p = strstr(save_filename, ".mp");
|
||
|
if (!p) p = strstr(save_filename, ".MP");
|
||
|
if (!p) p = strstr(save_filename, ".mP");
|
||
|
if (!p) p = strstr(save_filename, ".Mp");
|
||
|
if (!p)
|
||
|
{
|
||
|
StringCchCatA(save_filename, 256, ".mp3");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (p && *p && *p != ' ') p++;
|
||
|
if (p) *p = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (save_filename[0])
|
||
|
{
|
||
|
char buf[4096] = {0};
|
||
|
StringCchPrintfA(buf, 4096, "%s%s%s", config_http_save_dir, config_http_save_dir[strlen(config_http_save_dir) - 1] == '\\' ? "" : "\\", save_filename);
|
||
|
hFile = CreateFileA(buf, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
}
|
||
|
}
|
||
|
else save_filename[0] = 0;
|
||
|
constate = meta_interval ? 4 : 2;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// check if stream is seekable
|
||
|
if (strlen(str) >= 14 && !_strnicmp(str, "Accept-Ranges:", 14))
|
||
|
{
|
||
|
m_is_stream_seekable = true;
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "Content-Length:", 15))
|
||
|
{
|
||
|
char *p = str + 15; while (p && *p == ' ') p++;
|
||
|
if (!is_stream_seek)
|
||
|
mpeg_length = atoi(p);
|
||
|
m_is_stream_seekable = true;
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "content-type:", 13))
|
||
|
{
|
||
|
char *p = str + 13; while (p && *p == ' ') p++;
|
||
|
free(m_content_type);
|
||
|
m_content_type = _strdup(p);
|
||
|
if (!_strnicmp(m_content_type, "misc/ultravox",13))
|
||
|
{
|
||
|
stream_title_save[0] = 0;
|
||
|
stream_name[0]=0;
|
||
|
g_stream_title[0] = 0;
|
||
|
is_uvox = 1;
|
||
|
// TODO get this to id as SC2 stream if possible...
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "Server:", 7))
|
||
|
{
|
||
|
char *p = str + 7; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(server_name, p, sizeof(server_name));
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "ultravox-max-msg:", 17))
|
||
|
{
|
||
|
char *p = str + 17; while (p && *p == ' ') p++;
|
||
|
uvox_maxmsg = (p ? atoi(p) : 0);
|
||
|
if (uvox_maxmsg > UVOX_MAXMSG_CAP) // benski> security vulnerability fix, too high of value was causing an integer overflow (because we malloc uvox_maxmsg*2
|
||
|
uvox_maxmsg = UVOX_MAXMSG_CAP;
|
||
|
m_is_stream = 3;
|
||
|
|
||
|
}
|
||
|
else if (!_strnicmp(str, "Ultravox-SID:", 13))
|
||
|
{
|
||
|
char *p = str + 13; while (p && *p == ' ') p++;
|
||
|
uvox_sid = (p ? atoi(p) : 0);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Avg-Bitrate:", 21))
|
||
|
{
|
||
|
char *p = str + 21; while (p && *p == ' ') p++;
|
||
|
uvox_avgbr = (p ? atoi(p) : 0);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Max-Bitrate:", 21))
|
||
|
{
|
||
|
char *p = str + 21; while (p && *p == ' ') p++;
|
||
|
uvox_maxbr = (p ? atoi(p) : 0);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Bitrate:", 17))
|
||
|
{
|
||
|
char *p = str + 17; while (p && *p == ' ') p++;
|
||
|
uvox_avgbr = uvox_maxbr = (p ? atoi(p) : 0);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Title:", 15))
|
||
|
{
|
||
|
char *p = str + 15; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(stream_title_save, p, 580);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Genre:", 15))
|
||
|
{
|
||
|
char *p = str + 15; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(stream_genre, p, sizeof(stream_genre));
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-URL:", 13)/* && !strstr(str, "shoutcast.com")*/)
|
||
|
{
|
||
|
char *p = str + 13; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(stream_url, p, sizeof(stream_url));
|
||
|
DWORD_PTR dw = 0;
|
||
|
if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw);
|
||
|
m_is_stream = 3;
|
||
|
}
|
||
|
if (!_strnicmp(str, "Ultravox-Class-Type:", 19))
|
||
|
{
|
||
|
// id our stream for 'streamtype'
|
||
|
// added for 5.63 as is a better way to check for
|
||
|
// a SC2/UVOX2.1 stream when no metadata is sent
|
||
|
m_is_stream = 5;
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "icy-notice2:", 12))
|
||
|
{
|
||
|
char *p = str + 12; while (p && *p == ' ') p++;
|
||
|
char *p2 = (p ? strstr(p, "<BR>") : 0);
|
||
|
if (p2)
|
||
|
{
|
||
|
*p2 = 0;
|
||
|
lstrcpynA(server_name, p, sizeof(server_name));
|
||
|
}
|
||
|
m_is_stream=2;
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "content-disposition:", 20))
|
||
|
{
|
||
|
if (strstr(str, "filename="))
|
||
|
lstrcpynA(g_stream_title, strstr(str, "filename=") + 9, sizeof(g_stream_title));
|
||
|
lstrcpynA(stream_title_save, g_stream_title, 580);
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "icy-name:", 9))
|
||
|
{
|
||
|
char *p = str + 9; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(g_stream_title, p, sizeof(g_stream_title));
|
||
|
lstrcpynA(stream_title_save, g_stream_title, 580);
|
||
|
lstrcpynA(stream_name, g_stream_title, 256);
|
||
|
// m_is_stream = 2; // benski> cut: some things use icy-name as a hack to show a title - not a reliable indicator of a SHOUTcast stream
|
||
|
}
|
||
|
|
||
|
if (!_strnicmp(str, "icy-metaint:", 12))
|
||
|
{
|
||
|
char *p = str + 12; while (p && *p == ' ') p++;
|
||
|
meta_interval = (p ? atoi(p) : 0);
|
||
|
m_is_stream = 2;
|
||
|
}
|
||
|
if (!_strnicmp(str, "icy-genre:", 10))
|
||
|
{
|
||
|
char *p = str + 10; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(stream_genre, p, sizeof(stream_genre));
|
||
|
m_is_stream = 2;
|
||
|
}
|
||
|
if (!_strnicmp(str, "icy-url:", 8) && !strstr(str, "shoutcast.com"))
|
||
|
{
|
||
|
char *p = str + 8; while (p && *p == ' ') p++;
|
||
|
lstrcpynA(stream_url, p, sizeof(stream_url));
|
||
|
//if (!strncmp(stream_url,"hTtP",4))
|
||
|
//{
|
||
|
// DWORD dw;
|
||
|
// SendMessageTimeout(mod.hMainWindow,WM_USER,(WPARAM)0,241,SMTO_NORMAL,500,&dw);
|
||
|
//} // Removed by Tag, Annoying.
|
||
|
DWORD_PTR dw = 0;
|
||
|
if (stream_url[0]) SendMessageTimeout(mod.hMainWindow, WM_USER, (WPARAM)stream_url, IPC_MBOPEN, SMTO_NORMAL, 500, &dw);
|
||
|
m_is_stream = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (constate == 2) // check for id3v2
|
||
|
{
|
||
|
if (jnl_connection_receive_bytes_available(m_connection) >= 10)
|
||
|
{
|
||
|
char buf[10]={0};
|
||
|
constate = 4;
|
||
|
jnl_connection_peek(m_connection, buf, 10);
|
||
|
if (buf[0] == 'I'
|
||
|
&& buf[1] == 'D'
|
||
|
&& buf[2] == '3'
|
||
|
&& buf[3] != 255
|
||
|
&& buf[4] != 255
|
||
|
&& buf[6] < 0x80
|
||
|
&& buf[7] < 0x80
|
||
|
&& buf[8] < 0x80
|
||
|
&& buf[9] < 0x80)
|
||
|
{
|
||
|
jnl_connection_receive(m_connection, buf, 10);
|
||
|
m_id3v2_len = 10;
|
||
|
if (buf[5] & 0x10) // check for footer flag
|
||
|
m_id3v2_len += 10;
|
||
|
m_id3v2_len += ((int)buf[6]) << 21;
|
||
|
m_id3v2_len += ((int)buf[7]) << 14;
|
||
|
m_id3v2_len += ((int)buf[8]) << 7;
|
||
|
m_id3v2_len += ((int)buf[9]);
|
||
|
if (m_id3v2_len < 0) m_id3v2_len = 0;
|
||
|
if (mpeg_length && m_id3v2_len > mpeg_length)
|
||
|
{
|
||
|
m_id3v2_len = 0;
|
||
|
}
|
||
|
if (m_id3v2_len)
|
||
|
{
|
||
|
constate = 3;
|
||
|
stream_id3v2_read = 0;
|
||
|
if (m_id3v2_len < 16*1024*1024)
|
||
|
{
|
||
|
stream_id3v2_buf = (char*)malloc(10 + m_id3v2_len);
|
||
|
stream_id3v2_read = 10;
|
||
|
memcpy(stream_id3v2_buf, buf, 10);
|
||
|
}
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_READING_ID3,lastfn_status,256);
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (constate == 3) // id3v2 found
|
||
|
{
|
||
|
while ((int)stream_id3v2_read < m_id3v2_len)
|
||
|
{
|
||
|
char buf[1024]={0};
|
||
|
int btoread = m_id3v2_len - stream_id3v2_read;
|
||
|
if (btoread > sizeof(buf)) btoread = sizeof(buf);
|
||
|
|
||
|
int avail = (int)jnl_connection_receive_bytes_available(m_connection);
|
||
|
if (btoread > avail) btoread = avail;
|
||
|
if (!btoread) break;
|
||
|
|
||
|
if (stream_id3v2_buf)
|
||
|
stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, stream_id3v2_buf + stream_id3v2_read, btoread);
|
||
|
else
|
||
|
stream_id3v2_read += (uint32_t)jnl_connection_receive(m_connection, buf, btoread);
|
||
|
//stream_id3v2_read+=m_connection->ReceiveBytes(0,btoread);
|
||
|
}
|
||
|
|
||
|
if ((int)stream_id3v2_read >= m_id3v2_len)
|
||
|
{
|
||
|
if (stream_id3v2_buf)
|
||
|
{
|
||
|
if (m_is_stream_seekable /*m_is_stream != 2*/) /* don't want to do id3v2 on an internet stream */
|
||
|
{
|
||
|
EnterCriticalSection(&streamInfoLock);
|
||
|
info.Decode(stream_id3v2_buf, stream_id3v2_read);
|
||
|
// TODO: streamInfo = info;
|
||
|
LeaveCriticalSection(&streamInfoLock);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
}
|
||
|
constate = 4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (constate == 4) // check for xing header
|
||
|
{
|
||
|
if (GetContentLength() < 1 || is_stream_seek) constate = 5;
|
||
|
else
|
||
|
{
|
||
|
int avail = (int)jnl_connection_receive_bytes_available(m_connection);
|
||
|
if (avail > 4096 || jnl_connection_get_state(m_connection) == JNL_CONNECTION_STATE_CLOSED)
|
||
|
{
|
||
|
char buf[4096]={0};
|
||
|
jnl_connection_peek(m_connection, buf, sizeof(buf));
|
||
|
constate++;
|
||
|
{
|
||
|
delete m_vbr_hdr;
|
||
|
m_vbr_hdr = 0;
|
||
|
LAMEinfo lame;
|
||
|
lame.toc = m_vbr_toc;
|
||
|
m_vbr_frame_len = ReadLAMEinfo((unsigned char *)buf, &lame);
|
||
|
if (m_vbr_frame_len)
|
||
|
{
|
||
|
prepad = lame.encoderDelay;
|
||
|
postpad = lame.padding;
|
||
|
if (lame.flags&TOC_FLAG)
|
||
|
{
|
||
|
int x;
|
||
|
for (x = 0; x < 100; x++)
|
||
|
if (m_vbr_toc[x]) break;
|
||
|
if (x != 100)
|
||
|
m_vbr_flag = 1;
|
||
|
}
|
||
|
if (lame.flags&FRAMES_FLAG)
|
||
|
{
|
||
|
m_vbr_frames = lame.frames;
|
||
|
m_vbr_samples = Int32x32To64(lame.frames, lame.h_id ? 1152 : 576);
|
||
|
m_vbr_samples -= (prepad + postpad);
|
||
|
m_vbr_ms = MulDiv((int)m_vbr_samples, 1000, lame.samprate);
|
||
|
}
|
||
|
if (!m_vbr_frames) m_vbr_flag = 0;
|
||
|
jnl_connection_receive(m_connection, buf, m_vbr_frame_len);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vbr_hdr = new CVbriHeader;
|
||
|
m_vbr_frame_len = m_vbr_hdr->readVbriHeader((unsigned char *)buf);
|
||
|
if (m_vbr_frame_len)
|
||
|
{
|
||
|
m_vbr_bytes = m_vbr_hdr->getBytes();
|
||
|
m_vbr_frames = m_vbr_hdr->getNumFrames();
|
||
|
m_vbr_ms = m_vbr_hdr->getNumMS();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete m_vbr_hdr;
|
||
|
m_vbr_hdr = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// TODO OFL
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (constate == 5) // time to stream
|
||
|
{
|
||
|
while (1)
|
||
|
{
|
||
|
// Process any timed titles
|
||
|
|
||
|
int len = (int)jnl_connection_receive_bytes_available(m_connection);
|
||
|
if (meta_interval && meta_pos >= meta_interval)
|
||
|
{
|
||
|
unsigned char b;
|
||
|
if (len > 0 && jnl_connection_peek(m_connection, (char*)&b, 1) && len > (b << 4))
|
||
|
{
|
||
|
char metabuf[4096]={0};
|
||
|
jnl_connection_receive(m_connection, metabuf, 1);
|
||
|
jnl_connection_receive(m_connection, metabuf, b << 4);
|
||
|
processMetaData(metabuf, b << 4);
|
||
|
stream_metabytes_read += (b << 4) + 1;
|
||
|
meta_pos = 0;
|
||
|
}
|
||
|
else break;
|
||
|
}
|
||
|
else if (is_uvox)
|
||
|
{
|
||
|
if (!uvox_stream_data)
|
||
|
{
|
||
|
/* benski> this was a security vulnerability.
|
||
|
don't blindly multiply by 2 and pass to malloc
|
||
|
if uvox_maxmsg happens to be 2^31, multiplying by 2 turns it into 0!
|
||
|
CUT!!! uvox_stream_data = (unsigned char*)malloc(uvox_maxmsg * 2 + 4096);
|
||
|
*/
|
||
|
uvox_stream_data = (unsigned char*)malloc(SAFE_MALLOC_MATH(uvox_maxmsg * 2 + 4096, uvox_maxmsg));
|
||
|
}
|
||
|
if (uvox_stream_data_len < 1)
|
||
|
{
|
||
|
again:
|
||
|
if (len < 6) break;
|
||
|
jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6);
|
||
|
|
||
|
int uvox_qos = uvox_stream_data[1];
|
||
|
int classtype = (uvox_stream_data[2] << 8) | uvox_stream_data[3];
|
||
|
int uvox_len = (uvox_stream_data[4] << 8) | uvox_stream_data[5];
|
||
|
|
||
|
int uvox_class = (classtype >> 12) & 0xF;
|
||
|
int uvox_type = classtype & 0xFFF;
|
||
|
|
||
|
if (uvox_stream_data[0] != 0x5A || uvox_len > uvox_maxmsg)
|
||
|
{
|
||
|
jnl_connection_receive(m_connection, NULL, 1);
|
||
|
len--;
|
||
|
uvox_desyncs++;
|
||
|
|
||
|
goto again;
|
||
|
}
|
||
|
|
||
|
if (uvox_len + 7 > len) break;
|
||
|
// process uvox chunk
|
||
|
|
||
|
jnl_connection_peek(m_connection, (char*)uvox_stream_data, 6 + uvox_len + 1);
|
||
|
if (uvox_stream_data[6 + uvox_len])
|
||
|
{
|
||
|
jnl_connection_receive(m_connection, NULL, 1);
|
||
|
uvox_desyncs++;
|
||
|
len--;
|
||
|
goto again;
|
||
|
}
|
||
|
else if (uvox_class == 0x2 && uvox_type == 0x003)
|
||
|
{
|
||
|
// failover gayness
|
||
|
}
|
||
|
else if (uvox_class == 0x2 && uvox_type == 0x002)
|
||
|
{
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_STREAM_TERMINATED,lastfn_status,256);
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
|
||
|
jnl_connection_close(m_connection, 0);
|
||
|
}
|
||
|
else if (uvox_class == 0x2 && uvox_type == 0x001)
|
||
|
{
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_STREAM_TEMPORARILY_INTERRUPTED,lastfn_status,256);
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
}
|
||
|
|
||
|
else if (uvox_class == 0x3 && (uvox_type == 0x902 || uvox_type == 0x901)) // SHOUTcast 2 metadata
|
||
|
{
|
||
|
// id our stream for 'streamtype'
|
||
|
m_is_stream = 5;
|
||
|
|
||
|
// this will allow us to cope with multi-packet metadata messages
|
||
|
// (used to be needed when the old SC2 spec used an APIC tag to
|
||
|
// to send the artwork in the metadata message - now it's sent in
|
||
|
// in the uvox_class == 0x4 messages for stream and playing cases)
|
||
|
if (!uvox_stream_data[8])
|
||
|
{
|
||
|
char *mbuf = (char*)uvox_stream_data + 12;
|
||
|
if (mbuf)
|
||
|
{
|
||
|
unsigned long delay = 0;
|
||
|
|
||
|
if (uvox_avgbr)
|
||
|
{
|
||
|
long byterate = (uvox_avgbr / 8);
|
||
|
long bytes = 1024 * 11;
|
||
|
|
||
|
float localdelay = (float)bytes / (float)byterate;
|
||
|
delay = (long)localdelay * 1000;
|
||
|
}
|
||
|
|
||
|
// make sure that we've got a packet which is within the specs
|
||
|
// as id can only be 1-32, total parts can only be 1-32,
|
||
|
// id cannot be more than total parts and if not then skip it.
|
||
|
if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 ||
|
||
|
uvox_stream_data[11] > uvox_stream_data[9] ||
|
||
|
uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
TITLELISTTYPE *newtitle = newTitleListEntry();
|
||
|
newtitle->style = (uvox_type == 0x902 ? UVOX_METADATA_STYLE_SHOUTCAST2 : UVOX_METADATA_STYLE_AOLRADIO);
|
||
|
// we make sure to only copy up to the maximum metadata payload size
|
||
|
newtitle->part_len = min((uvox_len - 6), 16371);
|
||
|
memcpy(newtitle->title, mbuf, newtitle->part_len);
|
||
|
newtitle->part = uvox_stream_data[11];
|
||
|
newtitle->total_parts = uvox_stream_data[9];
|
||
|
newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (uvox_class == 0x4) // SHOUTcast 2 albumart
|
||
|
{
|
||
|
if (allow_scartwork && !uvox_stream_data[8])
|
||
|
{
|
||
|
char *mbuf = (char*)uvox_stream_data + 12;
|
||
|
if (mbuf)
|
||
|
{
|
||
|
// [0x4] [0|1] [00|01|02|03]
|
||
|
unsigned long delay = 0;
|
||
|
|
||
|
if (uvox_avgbr)
|
||
|
{
|
||
|
long byterate = (uvox_avgbr / 8);
|
||
|
long bytes = 1024 * 11;
|
||
|
|
||
|
float localdelay = (float)bytes / (float)byterate;
|
||
|
delay = (long)localdelay * 1000;
|
||
|
}
|
||
|
|
||
|
// make sure that we've got a packet which is within the specs
|
||
|
// as id can only be 1-32, total parts can only be 1-32,
|
||
|
// id cannot be more than total parts and if not then skip it.
|
||
|
if(uvox_stream_data[11] < 1 || uvox_stream_data[11] > 32 ||
|
||
|
uvox_stream_data[11] > uvox_stream_data[9] ||
|
||
|
uvox_stream_data[9] < 1 || uvox_stream_data[9] > 32)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
TITLELISTTYPE *newtitle = newTitleListEntry();
|
||
|
newtitle->style = (uvox_type & 0x100 ? UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK_PLAYING : UVOX_METADATA_STYLE_SHOUTCAST2_ARTWORK);
|
||
|
// we make sure to only copy up to the maximum metadata payload size
|
||
|
newtitle->part_len = min((uvox_len - 6), 16371);
|
||
|
memcpy(newtitle->title, mbuf, newtitle->part_len);
|
||
|
newtitle->part = uvox_stream_data[11];
|
||
|
newtitle->total_parts = uvox_stream_data[9];
|
||
|
newtitle->type = (uvox_type & 0x00FF);
|
||
|
newtitle->timer = (stream_bytes_read && mod.outMod ? delay + mod.outMod->GetOutputTime() : 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (uvox_class == 0x3 && (uvox_type == 0x001 || uvox_type == 0x002))
|
||
|
{
|
||
|
int w = uvox_type - 1; // should be ID?
|
||
|
int n = (uvox_stream_data[8] << 8) | uvox_stream_data[9];
|
||
|
if (n && n < 33)
|
||
|
{
|
||
|
int t = (uvox_stream_data[10] << 8) | uvox_stream_data[11];
|
||
|
if (t && t <= n)
|
||
|
{
|
||
|
int l = 0;
|
||
|
int a;
|
||
|
char *p = (char*)uvox_stream_data + 12; // this almost works
|
||
|
free(uvox_meta[w][t - 1]);
|
||
|
uvox_meta[w][t - 1] = _strdup(p);
|
||
|
for (a = 0;a < n;a++)
|
||
|
{
|
||
|
if (!uvox_meta[w][a]) break;
|
||
|
l += (int)strlen(uvox_meta[w][a]);
|
||
|
}
|
||
|
if (a == n)
|
||
|
{
|
||
|
char *outtext = (char*)malloc(l + 1);
|
||
|
p = outtext;
|
||
|
for (a = 0;a < n;a++)
|
||
|
{
|
||
|
lstrcpynA(p, uvox_meta[w][a], l + 1);
|
||
|
free(uvox_meta[w][a]);
|
||
|
uvox_meta[w][a] = 0;
|
||
|
p += strlen(p);
|
||
|
}
|
||
|
processMetaData(outtext, l + 1);
|
||
|
free(outtext);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if ((uvox_class == 0x7 && (uvox_type == 0x0 || uvox_type == 0x1))
|
||
|
|| (uvox_class == 0x8 && (uvox_type == 0x0 || uvox_type == 0x1 || uvox_type == 0x3)))
|
||
|
{
|
||
|
memcpy(uvox_stream_data, uvox_stream_data + 6, uvox_len);
|
||
|
uvox_stream_data_len = uvox_len;
|
||
|
uvox_last_message = uvox_class << 12 | uvox_type;
|
||
|
}
|
||
|
jnl_connection_receive(m_connection, NULL, 6 + uvox_len + 1);
|
||
|
|
||
|
uvox_message_cnt++;
|
||
|
}
|
||
|
if (uvox_stream_data_len > 0)
|
||
|
{
|
||
|
len = min(cbToRead, uvox_stream_data_len);
|
||
|
|
||
|
memcpy(pBuffer, uvox_stream_data, len);
|
||
|
if (pcbRead) *pcbRead = len;
|
||
|
stream_bytes_read += len;
|
||
|
if (len < uvox_stream_data_len)
|
||
|
{
|
||
|
memcpy(uvox_stream_data, uvox_stream_data + len, uvox_stream_data_len - len);
|
||
|
}
|
||
|
uvox_stream_data_len -= len;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
len = min(cbToRead, len);
|
||
|
if (meta_interval) len = min(meta_interval - meta_pos, len);
|
||
|
if (len > 0)
|
||
|
{
|
||
|
DWORD dw = 0;
|
||
|
len = (int)jnl_connection_receive(m_connection, (char*)pBuffer, len);
|
||
|
if (hFile != INVALID_HANDLE_VALUE) WriteFile(hFile, pBuffer, len, &dw, NULL);
|
||
|
if (pcbRead) *pcbRead = len;
|
||
|
meta_pos += len;
|
||
|
stream_bytes_read += len;
|
||
|
}
|
||
|
else if (pcbRead) *pcbRead = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int state = m_connection ? jnl_connection_get_state(m_connection) : JNL_CONNECTION_STATE_CLOSED;
|
||
|
if (state == JNL_CONNECTION_STATE_ERROR || state == JNL_CONNECTION_STATE_CLOSED)
|
||
|
{
|
||
|
if ((!pcbRead || !*pcbRead) && (!m_connection || jnl_connection_receive_bytes_available(m_connection) < 1))
|
||
|
{
|
||
|
fEof = 1;
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
}
|
||
|
else if (constate != 5 && constate != 4 && constate != 3 && timeout_start + 15000 < GetTickCount()) // 15 second net connect timeout
|
||
|
{
|
||
|
EnterCriticalSection(&g_lfnscs);
|
||
|
WASABI_API_LNGSTRING_BUF(IDS_TIMED_OUT,lastfn_status,256);
|
||
|
lastfn_status_err = 1;
|
||
|
LeaveCriticalSection(&g_lfnscs);
|
||
|
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
|
||
|
fEof = 1;
|
||
|
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
else if (m_full_buffer)
|
||
|
{
|
||
|
int len = cbToRead;
|
||
|
if ((uint64_t)len > (mpeg_length - m_full_buffer_pos))
|
||
|
{
|
||
|
len = (int)(mpeg_length - m_full_buffer_pos);
|
||
|
}
|
||
|
if (pcbRead) *pcbRead = len;
|
||
|
if (len)
|
||
|
{
|
||
|
memcpy(pBuffer, m_full_buffer + m_full_buffer_pos, len);
|
||
|
m_full_buffer_pos += len;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fEof = true;
|
||
|
return NErr_EndOfFile;
|
||
|
}
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
if ((uint64_t)cbToRead >= (mpeg_length - file_position))
|
||
|
{
|
||
|
cbToRead = (int)(mpeg_length - file_position);
|
||
|
fEof = true;
|
||
|
}
|
||
|
|
||
|
if (cbToRead == 0)
|
||
|
{
|
||
|
if (pcbRead)
|
||
|
*pcbRead = 0;
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
|
||
|
DWORD dwRead = 0;
|
||
|
bSuccess = ReadFile(hFile, pBuffer, cbToRead, &dwRead, NULL);
|
||
|
|
||
|
if (bSuccess)
|
||
|
{
|
||
|
file_position += dwRead;
|
||
|
// update pcbRead
|
||
|
if (pcbRead)
|
||
|
*pcbRead = dwRead;
|
||
|
|
||
|
// check for EOF
|
||
|
if (dwRead == 0)
|
||
|
fEof = true;
|
||
|
|
||
|
return NErr_Success;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// error reading from file
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// no valid file handle
|
||
|
return NErr_Error;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// IsEof
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
bool CGioFile::IsEof() const
|
||
|
{
|
||
|
return fEof;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// GetContentLength
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
DWORD CGioFile::GetContentLength(void) const
|
||
|
{
|
||
|
DWORD dwSize = 0 ;
|
||
|
|
||
|
dwSize = (DWORD)mpeg_length;
|
||
|
|
||
|
return dwSize ;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// GetCurrentPosition
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
DWORD CGioFile::GetCurrentPosition(void) const
|
||
|
{
|
||
|
DWORD dwPos = 0;
|
||
|
|
||
|
if (m_is_stream)
|
||
|
{
|
||
|
dwPos = (DWORD)stream_bytes_read;
|
||
|
}
|
||
|
else if (m_full_buffer)
|
||
|
{
|
||
|
dwPos = (DWORD)m_full_buffer_pos;
|
||
|
}
|
||
|
else if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
dwPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
|
||
|
dwPos -= (DWORD)(mpeg_position + peekBuffer.size());
|
||
|
}
|
||
|
|
||
|
return dwPos ;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------*
|
||
|
// SetCurrentPosition
|
||
|
//-------------------------------------------------------------------------*
|
||
|
|
||
|
void CGioFile::SetCurrentPosition(long dwPos, int How) // GIO_FILE_BEGIN only
|
||
|
{
|
||
|
fEof = false;
|
||
|
if (m_full_buffer)
|
||
|
{
|
||
|
m_full_buffer_pos = dwPos;
|
||
|
if (m_full_buffer_pos < 0) m_full_buffer_pos = 0;
|
||
|
if (m_full_buffer_pos > mpeg_length) m_full_buffer_pos = mpeg_length;
|
||
|
}
|
||
|
else if (hFile != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
Seek64(hFile, mpeg_position + dwPos, FILE_BEGIN);
|
||
|
file_position = dwPos;
|
||
|
peekBuffer.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CGioFile::GetHeaderOffset()
|
||
|
{
|
||
|
return (int)mpeg_position;
|
||
|
}
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
void CGioFile::Seek(int posms, int br)
|
||
|
{
|
||
|
int offs = 0;
|
||
|
if (m_vbr_hdr)
|
||
|
{
|
||
|
offs = m_vbr_hdr->seekPointByTime((float)posms);
|
||
|
}
|
||
|
else if (!m_vbr_flag)
|
||
|
offs = MulDiv(posms, br, 8);
|
||
|
else
|
||
|
{
|
||
|
int ms = 0;
|
||
|
int fl = GetContentLength();
|
||
|
if (!m_vbr_frames)
|
||
|
{
|
||
|
ms = MulDiv(fl, 8 * 1000, br);
|
||
|
}
|
||
|
else ms = m_vbr_ms;
|
||
|
offs = SeekPoint(m_vbr_toc, fl, (float)posms / ((float)ms / 100.0f));
|
||
|
}
|
||
|
if (m_is_stream)
|
||
|
{
|
||
|
if (GetContentLength() > 0)
|
||
|
{
|
||
|
if (m_connection && m_is_stream_seekable)
|
||
|
{
|
||
|
jnl_connection_release(m_connection);
|
||
|
m_connection = NULL;
|
||
|
m_is_stream_seek = true;
|
||
|
m_seek_reset = false;
|
||
|
doConnect(NULL, offs);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetCurrentPosition(offs, GIO_FILE_BEGIN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CGioFile::IsSeekable()
|
||
|
{
|
||
|
return !m_is_stream || GetContentLength() > 0;
|
||
|
}
|
||
|
|
||
|
void CGioFile::GetStreamInfo(wchar_t *obuf, size_t len)
|
||
|
{
|
||
|
if (m_is_stream)
|
||
|
{
|
||
|
wchar_t langbuf[2048]={0};
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_NETWORK_RECEIVED_X_BYTES, langbuf, 2048), stream_bytes_read + stream_metabytes_read);
|
||
|
if (server_name[0])
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SERVER, langbuf, 2048), AutoWide(server_name,CP_UTF8));
|
||
|
if (m_content_type && m_content_type[0])
|
||
|
{
|
||
|
if(is_uvox)
|
||
|
{
|
||
|
// report the actual content type instead of just misc/ultravox to make it easier to see what the stream type is (helps debugging)
|
||
|
static const int MP3_DATA = 0x7000;
|
||
|
static const int VLB_DATA = 0x8000;
|
||
|
static const int AAC_LC_DATA = 0x8001;
|
||
|
static const int AACP_DATA = 0x8003;
|
||
|
static const int OGG_DATA = 0x8004;
|
||
|
switch(uvox_last_message)
|
||
|
{
|
||
|
case MP3_DATA:
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/mpeg");
|
||
|
break;
|
||
|
|
||
|
case VLB_DATA:
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/vlb");
|
||
|
break;
|
||
|
|
||
|
case AAC_LC_DATA:
|
||
|
case AACP_DATA:
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/aacp");
|
||
|
break;
|
||
|
|
||
|
case OGG_DATA:
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), L"audio/ogg");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_TYPE, langbuf, 2048), AutoWide(m_content_type,CP_UTF8));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (is_uvox)
|
||
|
{
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SYNC, langbuf, 2048), uvox_message_cnt, uvox_desyncs);
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_DATA_MESSAGE, langbuf, 2048), uvox_last_message);
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ULTRAVOX_SID_AVGBR_MAXBR, langbuf, 2048), uvox_sid, uvox_avgbr, uvox_maxbr);
|
||
|
}
|
||
|
|
||
|
if (stream_metabytes_read)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_RECEIVED, langbuf, 2048), stream_metabytes_read);
|
||
|
if (meta_interval)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_METADATA_INTERVAL, langbuf, 2048), meta_interval);
|
||
|
if (m_id3v2_len)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_ID3v2_TAG, langbuf, 2048), m_id3v2_len);
|
||
|
if (m_vbr_frame_len)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_VBR_LEADING_FRAME, langbuf, 2048), m_vbr_frame_len);
|
||
|
if (stream_title_save[0])
|
||
|
{
|
||
|
wchar_t name[580]={0};
|
||
|
ConvertTryUTF8(stream_title_save, name, 580);
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_STREAM_NAME, langbuf, 2048), name/*AutoWide(stream_title_save,CP_UTF8)*/);
|
||
|
}
|
||
|
if (last_title_sent[0])
|
||
|
{
|
||
|
wchar_t title[256]={0};
|
||
|
ConvertTryUTF8(last_title_sent, title, 256);
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CURRENT_TITLE, langbuf, 2048), title);
|
||
|
}
|
||
|
|
||
|
if (mpeg_length)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_CONTENT_LENGTH, langbuf, 2048), mpeg_length);
|
||
|
if (save_filename[0] && hFile != INVALID_HANDLE_VALUE)
|
||
|
StringCchPrintfEx(obuf, len, &obuf, &len, 0, WASABI_API_LNGSTRINGW_BUF(IDS_SAVING_TO, langbuf, 2048), AutoWide(save_filename,CP_UTF8));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline const wchar_t *IncSafe(const wchar_t *val, int x)
|
||
|
{
|
||
|
while (x--)
|
||
|
{
|
||
|
if (val && *val)
|
||
|
val++;
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
void CGioFile::ReadiTunesGaps()
|
||
|
{
|
||
|
if (info.HasData() && !prepad && !postpad)
|
||
|
{
|
||
|
wchar_t str[128] = {0};
|
||
|
if (info.GetString("pregap", str, 128) == 1)
|
||
|
prepad = _wtoi(str);
|
||
|
|
||
|
str[0]=0;
|
||
|
if (info.GetString("postgap", str, 128) == 1)
|
||
|
postpad = _wtoi(str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define CBCLASS CGioFile
|
||
|
START_DISPATCH;
|
||
|
CB( MPEGSTREAM_PEEK, Peek )
|
||
|
CB( MPEGSTREAM_READ, Read )
|
||
|
CB( MPEGSTREAM_EOF, EndOf )
|
||
|
CB( MPEGSTREAM_GAIN, GetGain )
|
||
|
END_DISPATCH;
|
||
|
#undef CBCLASS
|