From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/replicant/http/HTTPPlayback.cpp | 325 ++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 Src/replicant/http/HTTPPlayback.cpp (limited to 'Src/replicant/http/HTTPPlayback.cpp') diff --git a/Src/replicant/http/HTTPPlayback.cpp b/Src/replicant/http/HTTPPlayback.cpp new file mode 100644 index 00000000..01200e5a --- /dev/null +++ b/Src/replicant/http/HTTPPlayback.cpp @@ -0,0 +1,325 @@ +#include "http/api.h" +#include "HTTPPlayback.h" +#include "http/svc_http_demuxer.h" +#include "service/ifc_servicefactory.h" +#include +#ifdef _WIN32 +#include "nu/AutoChar.h" +#endif +#include "nu/strsafe.h" +#include "nx/nxsleep.h" +#ifdef __ANDROID__ +#include // TODO: replace with generic logging API +#else +#define ANDROID_LOG_INFO 0 +#define ANDROID_LOG_ERROR 1 +void __android_log_print(int, const char *, const char *, ...) +{ +} +#endif +#include + +HTTPPlayback::HTTPPlayback() +{ + http=0; + demuxer=0; +} + +int HTTPPlayback::Initialize(nx_uri_t url, ifc_player *player) +{ + int ret = PlaybackBase::Initialize(url, player); + if (ret != NErr_Success) + return ret; + http=0; + demuxer=0; + ifc_playback::Retain(); /* the thread needs to hold a reference to this object so that it doesn't disappear out from under us */ + NXThreadCreate(&playback_thread, HTTPPlayerThreadFunction, this); + return NErr_Success; +} + +HTTPPlayback::~HTTPPlayback() +{ + if (demuxer) + demuxer->Release(); + if (http) + jnl_http_release(http); +} + +nx_thread_return_t HTTPPlayback::HTTPPlayerThreadFunction(nx_thread_parameter_t param) +{ + HTTPPlayback *playback = (HTTPPlayback *)param; + NXThreadCurrentSetPriority(NX_THREAD_PRIORITY_PLAYBACK); + nx_thread_return_t ret = playback->DecodeLoop(); + playback->ifc_playback::Release(); + return ret; +} + +int HTTPPlayback::Init() +{ + http = jnl_http_create(2*1024*1024, 0); + if (!http) + return NErr_OutOfMemory; + + return NErr_Success; +} + +static void SetupHTTP(jnl_http_t http) +{ + char accept[1024], user_agent[256]; + accept[0]=0; + user_agent[0]=0; + size_t accept_length=sizeof(accept)/sizeof(*accept); + size_t user_agent_length=sizeof(user_agent)/sizeof(*user_agent); + char *p_accept = accept, *p_user_agent=user_agent; + + const char *application_user_agent = WASABI2_API_APP->GetUserAgent(); + StringCchCopyExA(p_user_agent, user_agent_length, application_user_agent, &p_user_agent, &user_agent_length, 0); + + GUID http_demuxer_guid = svc_http_demuxer::GetServiceType(); + ifc_serviceFactory *sf; + size_t n = 0; + while (sf = WASABI2_API_SVC->EnumService(http_demuxer_guid, n++)) + { + svc_http_demuxer *l = (svc_http_demuxer*)sf->GetInterface(); + if (l) + { + const char *this_accept; + size_t i=0; + while (this_accept=l->EnumerateAcceptedTypes(i++)) + { + if (accept == p_accept) // first one added + StringCchCopyExA(p_accept, accept_length, this_accept, &p_accept, &accept_length, 0); + else + StringCchPrintfExA(p_accept, accept_length, &p_accept, &accept_length, 0, ", %s", this_accept); + } + + const char *this_user_agent = l->GetUserAgent(); + if (this_user_agent) + { + StringCchPrintfExA(p_user_agent, user_agent_length, &p_user_agent, &user_agent_length, 0, " %s", this_user_agent); + } + + l->CustomizeHTTP(http); + l->Release(); + } + } + if (accept != p_accept) + jnl_http_addheadervalue(http, "Accept", accept); + jnl_http_addheadervalue(http, "User-Agent", user_agent); + jnl_http_addheadervalue(http, "Connection", "close"); + +} + +static NError FindDemuxer(nx_uri_t uri, jnl_http_t http, ifc_http_demuxer **demuxer) +{ + GUID http_demuxer_guid = svc_http_demuxer::GetServiceType(); + ifc_serviceFactory *sf; + + bool again; + int pass=0; + do + { + size_t n = 0; + again=false; + while (sf = WASABI2_API_SVC->EnumService(http_demuxer_guid, n++)) + { + svc_http_demuxer *l = (svc_http_demuxer*)sf->GetInterface(); + if (l) + { + NError err = l->CreateDemuxer(uri, http, demuxer, pass); + if (err == NErr_Success) + return NErr_Success; + + if (err == NErr_TryAgain) + again=true; + } + } + pass++; + } while (again); + return NErr_NoMatchingImplementation; +} + +int HTTPPlayback::Internal_Connect(uint64_t byte_position) +{ + int http_ver = byte_position?1:0; + + if (byte_position != 0) + { + char str[512]; + StringCchPrintfA(str, 512, "Range: bytes=%llu-", byte_position); + + jnl_http_addheader(http, str); + } + //jnl_http_allow_accept_all_reply_codes(http); +#ifdef _WIN32 + jnl_http_connect(http, AutoChar(filename->string), http_ver, "GET"); +#else + jnl_http_connect(http, filename->string, http_ver, "GET"); +#endif + + /* wait for connection */ + time_t start_time = time(0); + + int http_status; + do + { + int ret = PlaybackBase::Sleep(10, PlaybackBase::WAKE_STOP); + if (ret == PlaybackBase::WAKE_STOP) + return NErr_Interrupted; + + ret = jnl_http_run(http); + if (ret == HTTPGET_RUN_ERROR) + return NErr_ConnectionFailed; + if (start_time + 15 < time(0)) + return NErr_TimedOut; + + http_status = jnl_http_get_status(http); + } while (http_status == HTTPGET_STATUS_CONNECTING || http_status == HTTPGET_STATUS_READING_HEADERS); + + if (http_status == HTTPGET_STATUS_ERROR) + { + switch(jnl_http_getreplycode(http)) + { + case 400: + return NErr_BadRequest; + case 401: + // TODO: deal with this specially + return NErr_Unauthorized; + case 403: + // TODO: deal with this specially? + return NErr_Forbidden; + case 404: + return NErr_NotFound; + case 405: + return NErr_BadMethod; + case 406: + return NErr_NotAcceptable; + case 407: + // TODO: deal with this specially + return NErr_ProxyAuthenticationRequired; + case 408: + return NErr_RequestTimeout; + case 409: + return NErr_Conflict; + case 410: + return NErr_Gone; + case 500: + return NErr_InternalServerError; + case 503: + return NErr_ServiceUnavailable; + + default: + return NErr_ConnectionFailed; + + } + } + return NErr_Success; +} + +nx_thread_return_t HTTPPlayback::DecodeLoop() +{ + player->OnLoaded(filename); + + int ret = Init(); + if (ret != NErr_Success) + { + player->OnError(ret); + return 0; + } + + SetupHTTP(http); + + /* connect, then find an ifc_http_demuxer */ + ret = Internal_Connect(0); + + if (ret == NErr_Success && FindDemuxer(filename, http, &demuxer) == NErr_Success && demuxer) + { + /* turn control over to the demuxer */ + ret = demuxer->Run(this, player, secondary_parameters); + if (ret == NErr_EndOfFile) + { + /* TODO: re-implement the individual demuxers so they keep calling set position for a while */ + player->OnClosed(); + return 0; + } + } + else if (ret == NErr_Interrupted) + { + player->OnStopped(); + return 0; + } + else if (ret == NErr_TimedOut) + { + player->OnError(ret); + return 0; + } + else if (ret == NErr_Success) + { + player->OnError(NErr_NoMatchingImplementation); + return 0; + } + else + { + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[http] error: %d, reply code: %d", ret, jnl_http_getreplycode(http)); + player->OnError(ret); + return 0; + } + + return 0; +} + +int HTTPPlayback::HTTP_Wake(int mask) +{ + return PlaybackBase::Wake(mask); +} + +int HTTPPlayback::HTTP_Check(int mask) +{ + return PlaybackBase::Check(mask); +} + +int HTTPPlayback::HTTP_Wait(unsigned int milliseconds, int mask) +{ + return PlaybackBase::Wait(milliseconds, mask); +} + +int HTTPPlayback::HTTP_Sleep(int milliseconds, int mask) +{ + return PlaybackBase::Sleep(milliseconds, mask); +} + +Agave_Seek *HTTPPlayback::HTTP_GetSeek() +{ + return PlaybackBase::GetSeek(); +} + +void HTTPPlayback::HTTP_FreeSeek(Agave_Seek *seek) +{ + PlaybackBase::FreeSeek(seek); +} + +int HTTPPlayback::HTTP_Seek(uint64_t byte_position) +{ + jnl_http_reset_headers(http); + SetupHTTP(http); + return Internal_Connect(byte_position); +} +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#endif + +int HTTPPlayback::HTTP_Seekable() +{ + const char *accept_ranges = jnl_http_getheader(http, "accept-ranges"); + if (accept_ranges && !strcasecmp(accept_ranges, "none")) + return NErr_False; /* server says it doesn't accept ranges */ + + /* note that not having an accept-ranges header doesn't necessary mean it's not seekable. see RFC2616 14.5 */ + return NErr_True; +} + +int HTTPPlayback::HTTP_AudioOpen(const ifc_audioout::Parameters *format, ifc_audioout **out_output) +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[http] output_service=%x", output_service); + return output_service->AudioOpen(format, player, secondary_parameters, out_output); +} -- cgit