summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config.h2
-rw-r--r--src/http/http.c (renamed from src/http.c)962
-rw-r--r--src/http/http.h (renamed from src/http.h)0
-rw-r--r--src/libpiano/response.c2
-rw-r--r--src/main.c11
-rw-r--r--src/main.h4
-rw-r--r--src/player/backends/direct_show.c464
-rw-r--r--src/player/backends/media_foundation.cpp1110
-rw-r--r--src/player/backends/media_foundation.h115
-rw-r--r--src/player/backends/utility/com_ptr.h148
-rw-r--r--src/player/backends/utility/optional.h517
-rw-r--r--src/player/player2.c211
-rw-r--r--src/player/player2.h (renamed from src/player2.h)45
-rw-r--r--src/player/player2_private.h35
-rw-r--r--src/player2.c409
-rw-r--r--src/settings.c5
-rw-r--r--src/settings.h1
-rw-r--r--src/ui.h2
-rw-r--r--src/ui_act.c4
19 files changed, 3126 insertions, 921 deletions
diff --git a/src/config.h b/src/config.h
index dc82d80..8e874ab 100644
--- a/src/config.h
+++ b/src/config.h
@@ -4,7 +4,7 @@
/* package name */
#define PACKAGE "pianobar"
-#define VERSION "2015.08.25"
+#define VERSION "2015.12.05"
#define TITLE "Pianobar"
diff --git a/src/http.c b/src/http/http.c
index 2c86b44..dd64ad1 100644
--- a/src/http.c
+++ b/src/http/http.c
@@ -1,481 +1,481 @@
-/*
-Copyright (c) 2015
- Michał Cichoń <thedmd@interia.pl>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-
-#include "config.h"
-#include "http.h"
-#include <Windows.h>
-#include <winhttp.h>
-#pragma comment(lib, "winhttp.lib")
-
-struct _http_t {
- HINTERNET session;
- HINTERNET connection;
- wchar_t* endpoint;
- wchar_t* securePort;
- wchar_t* autoProxy;
- wchar_t* proxy;
- wchar_t* proxyUsername;
- wchar_t* proxyPassword;
- char* error;
-};
-
-static char* HttpToString(const wchar_t* wideString, int size);
-static wchar_t* HttpToWideString(const char* string, int size);
-static bool HttpCreateConnection (http_t http);
-static void HttpCloseConnection (http_t http);
-static void HttpSetLastError (http_t http, const char* message);
-static void HttpSetLastErrorFromWinHttp (http_t http);
-static char* HttpFormatWinApiError (DWORD errorCode, HINSTANCE module);
-static char* HttpFormatWinHttpError (DWORD errorCode);
-static void HttpClearProxy (http_t http);
-
-# define WINHTTP_SAFE(condition) do { if (condition) break; HttpSetLastErrorFromWinHttp (http); return false; } while (false)
-# define WINHTTP_SAFE_DONE(condition) do { if (condition) break; HttpSetLastErrorFromWinHttp (http); goto done; } while (false)
-
-static char* HttpToString(const wchar_t* wideString, int size) {
- int utfSize = WideCharToMultiByte(CP_UTF8, 0, wideString, size, NULL, 0, NULL, NULL);
- char* utfMessage = malloc(utfSize + 1);
- if (utfMessage) {
- utfMessage[utfSize] = 0;
- WideCharToMultiByte(CP_UTF8, 0, wideString, size, utfMessage, utfSize, NULL, NULL);
- }
- return utfMessage;
-}
-
-static wchar_t* HttpToWideString(const char* string, int size) {
- int wideSize = MultiByteToWideChar(CP_UTF8, 0, string, size, NULL, 0);
- int wideBytes = (wideSize + 1) * sizeof(wchar_t);
- wchar_t* wideMessage = malloc(wideBytes);
- if (wideMessage) {
- wideMessage[wideSize] = 0;
- MultiByteToWideChar(CP_UTF8, 0, string, size, wideMessage, wideSize);
- }
- return wideMessage;
-}
-
-
-static bool HttpCreateConnection (http_t http) {
- INTERNET_PORT defaultPort = INTERNET_DEFAULT_PORT;
-
- HttpCloseConnection (http);
-
- http->session = WinHttpOpen(
- L"WinHTTP/1.0",
- WINHTTP_ACCESS_TYPE_NO_PROXY,
- WINHTTP_NO_PROXY_NAME,
- WINHTTP_NO_PROXY_BYPASS,
- 0);
- WINHTTP_SAFE(http->session != NULL);
-
- WinHttpSetTimeouts(http->session,
- 60 * 1000, // DNS time-out
- 60 * 1000, // connect time-out
- 30 * 1000, // send time-out
- 30 * 1000); // receive time-out
-
- http->connection = WinHttpConnect(
- http->session,
- http->endpoint,
- defaultPort,
- 0);
- WINHTTP_SAFE(http->connection != NULL);
-
- return true;
-}
-
-static void HttpCloseConnection (http_t http) {
- if (http->connection) {
- WinHttpCloseHandle(http->connection);
- http->connection = NULL;
- }
-
- if (http->session) {
- WinHttpCloseHandle(http->session);
- http->session = NULL;
- }
-}
-
-static void HttpSetLastError (http_t http, const char* message) {
- free(http->error);
- http->error = NULL;
-
- if (message)
- http->error = strdup(message);
-}
-
-static void HttpSetLastErrorFromWinHttp (http_t http) {
- free(http->error);
- http->error = NULL;
-
- DWORD error = GetLastError();
- if (error)
- http->error = HttpFormatWinHttpError(error);
-}
-
-static char* HttpFormatWinApiError (DWORD errorCode, HINSTANCE module) {
- const int source_flag = module ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM;
-
- HLOCAL buffer = NULL;
- int bufferLength = FormatMessageW(
- FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | source_flag,
- (void*)module,
- errorCode,
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPWSTR)&buffer,
- 0,
- NULL);
-
- if (bufferLength > 0) {
- char* message;
-
- wchar_t* wideMessage = (wchar_t*)buffer;
-
- /* Drop new line from the end. */
- wchar_t* wideMessageBack = wideMessage + bufferLength - 1;
- while (wideMessageBack > wideMessage && (*wideMessageBack == '\r' || *wideMessageBack == '\n'))
- --wideMessageBack;
-
- message = HttpToString (wideMessage, wideMessageBack - wideMessage + 1);
-
- LocalFree(buffer);
-
- return message;
- }
- else
- return NULL;
-}
-
-static char* HttpFormatWinHttpError (DWORD errorCode) {
- if (errorCode >= WINHTTP_ERROR_BASE && errorCode <= WINHTTP_ERROR_LAST) {
- HMODULE module = GetModuleHandleW(L"WinHTTP.dll");
- if (module) {
- char* message = HttpFormatWinApiError(errorCode, module);
- if (message)
- return message;
- }
- }
-
- return HttpFormatWinApiError(errorCode, NULL);
-}
-
-bool HttpInit(http_t* http, const char* endpoint, const char* securePort) {
- http_t out = malloc(sizeof(struct _http_t));
- if (!out)
- return false;
- memset(out, 0, sizeof(struct _http_t));
-
- out->endpoint = HttpToWideString(endpoint, -1);
- out->securePort = HttpToWideString(securePort, -1);
-
- if (!HttpCreateConnection (out)) {
- HttpDestroy (out);
- return false;
- }
-
- *http = out;
- return true;
-}
-
-void HttpDestroy(http_t http) {
- if (http) {
- free(http->endpoint);
- free(http->securePort);
- http->endpoint = NULL;
- http->securePort = NULL;
- HttpCloseConnection (http);
- HttpClearProxy (http);
- }
- free(http);
-}
-
-static void HttpClearProxy (http_t http) {
- if (http->autoProxy) {
- free(http->autoProxy);
- http->autoProxy = NULL;
- }
-
- if (http->proxy) {
- free(http->proxy);
- http->proxy = NULL;
- }
-
- if (http->proxyUsername) {
- free(http->proxyUsername);
- http->proxyUsername = NULL;
- }
-
- if (http->proxyPassword) {
- free(http->proxyPassword);
- http->proxyPassword = NULL;
- }
-}
-
-bool HttpSetAutoProxy (http_t http, const char* url) {
- HttpClearProxy (http);
- if (HttpSetProxy (http, url)) {
- http->autoProxy = http->proxy;
- http->proxy = NULL;
- return true;
- }
- else
- return false;
-}
-
-bool HttpSetProxy (http_t http, const char* url) {
- URL_COMPONENTS urlComponents;
- wchar_t* wideUrl = NULL;
- wchar_t* wideUrl2 = NULL;
- wchar_t* wideUsername = NULL;
- wchar_t* widePassword = NULL;
-
- ZeroMemory(&urlComponents, sizeof(urlComponents));
- urlComponents.dwStructSize = sizeof(urlComponents);
- urlComponents.dwUserNameLength = -1;
- urlComponents.dwPasswordLength = -1;
-
- wideUrl = HttpToWideString(url, -1);
- if (WinHttpCrackUrl(wideUrl, wcslen(wideUrl), 0, &urlComponents)) {
- if (urlComponents.lpszUserName && urlComponents.dwUserNameLength > 0) {
- wideUsername = wcsdup(urlComponents.lpszUserName);
- wideUsername[urlComponents.dwUserNameLength] = 0;
- }
- if (urlComponents.lpszPassword && urlComponents.dwPasswordLength > 0) {
- widePassword = wcsdup(urlComponents.lpszPassword);
- widePassword[urlComponents.dwPasswordLength] = 0;
- }
- }
-
- ZeroMemory(&urlComponents, sizeof(urlComponents));
- urlComponents.dwStructSize = sizeof(urlComponents);
- urlComponents.dwHostNameLength = -1;
- urlComponents.dwUrlPathLength = -1;
-
- if (!WinHttpCrackUrl(wideUrl, wcslen(wideUrl), 0, &urlComponents)) {
- free(wideUsername);
- free(widePassword);
- return false;
- }
-
- if (urlComponents.lpszHostName && urlComponents.dwHostNameLength > 0) {
- wideUrl2 = wcsdup(urlComponents.lpszHostName);
- wideUrl2[urlComponents.lpszUrlPath - urlComponents.lpszHostName] = 0;
- }
-
- free(wideUrl);
-
- HttpClearProxy(http);
- http->proxy = wideUrl2;
- http->proxyUsername = wideUsername;
- http->proxyPassword = widePassword;
- return true;
-}
-
-bool HttpRequest(http_t http, PianoRequest_t * const request) {
- HINTERNET handle = NULL;
- wchar_t* wideQuery = NULL;
- bool requestSent = false;
- bool complete = false;
- int retryLimit = 3;
- size_t responseDataSize;
-
- wideQuery = HttpToWideString(request->urlPath, -1);
- WINHTTP_SAFE_DONE(wideQuery != NULL);
-
- handle = WinHttpOpenRequest(
- http->connection,
- L"POST",
- wideQuery,
- L"HTTP/1.1",
- WINHTTP_NO_REFERER,
- WINHTTP_DEFAULT_ACCEPT_TYPES,
- request->secure ? WINHTTP_FLAG_SECURE : 0);
- WINHTTP_SAFE_DONE(handle != NULL);
-
- if (http->proxy || http->autoProxy) {
- wchar_t* fullUrl;
- DWORD fullUrlSize = 0;
- WINHTTP_PROXY_INFO proxyInfo;
- bool success;
-
- if (http->autoProxy) {
- WINHTTP_AUTOPROXY_OPTIONS proxyOptions = { 0 };
-
- success = WinHttpQueryOption(request, WINHTTP_OPTION_URL, NULL, &fullUrlSize);
- WINHTTP_SAFE(!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
- fullUrl = calloc(1, fullUrlSize + 1);
- success = WinHttpQueryOption(request, WINHTTP_OPTION_URL, fullUrl, &fullUrlSize);
- if (!success) {
- free(fullUrl);
- WINHTTP_SAFE(success);
- }
-
- proxyOptions.lpszAutoConfigUrl = http->autoProxy;
- proxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
-
- if (!(success = WinHttpGetProxyForUrl(http->session, fullUrl, &proxyOptions, &proxyInfo))) {
- proxyOptions.fAutoLogonIfChallenged = true;
- success = WinHttpGetProxyForUrl(http->session, fullUrl, &proxyOptions, &proxyInfo);
- }
-
- if (!success) {
- free(fullUrl);
- WINHTTP_SAFE(success);
- }
- }
- else {
- proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
- proxyInfo.lpszProxy = http->proxy;
- proxyInfo.lpszProxyBypass = NULL;
- }
-
- WINHTTP_SAFE(WinHttpSetOption(handle,
- WINHTTP_OPTION_PROXY,
- &proxyInfo, sizeof(proxyInfo)));
-
- if (http->proxyUsername && http->proxyPassword) {
- WINHTTP_SAFE(WinHttpSetCredentials(handle,
- WINHTTP_AUTH_TARGET_PROXY,
- WINHTTP_AUTH_SCHEME_BASIC,
- http->proxyUsername,
- http->proxyPassword,
- NULL));
- }
- }
-
- while (retryLimit > 0) {
- DWORD errorCode, statusCode, statusCodeSize;
- bool succeeded = false;
- bool retry = false;
-
- if (!requestSent) {
- size_t postDataSize = strlen(request->postData);
- succeeded = WinHttpSendRequest(handle,
- WINHTTP_NO_ADDITIONAL_HEADERS,
- 0,
- request->postData,
- postDataSize,
- postDataSize,
- 0);
-
- if (succeeded)
- requestSent = true;
- }
-
- if (requestSent)
- succeeded = WinHttpReceiveResponse(handle, NULL);
-
- errorCode = GetLastError();
-
- statusCode = 0;
- statusCodeSize = sizeof(statusCode);
- if (!WinHttpQueryHeaders(handle,
- WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
- WINHTTP_HEADER_NAME_BY_INDEX,
- &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) {
- statusCode = 0;
- }
-
- if (succeeded && statusCode == 407) {
- requestSent = false;
- retry = true;
- }
- else {
- if (errorCode == ERROR_SUCCESS)
- break;
-
- switch (errorCode) {
- case ERROR_WINHTTP_RESEND_REQUEST:
- requestSent = false;
- /* pass trough */
-
- case ERROR_WINHTTP_NAME_NOT_RESOLVED:
- case ERROR_WINHTTP_CANNOT_CONNECT:
- case ERROR_WINHTTP_TIMEOUT:
- retry = true;
- break;
-
- default:
- HttpSetLastErrorFromWinHttp (http);
- goto done;
- }
- }
-
- if (retry)
- --retryLimit;
- }
-
- responseDataSize = 0;
- while (retryLimit > 0)
- {
- DWORD bytesLeft;
- char* writePtr;
-
- DWORD bytesAvailable = 0;
- if (!WinHttpQueryDataAvailable(handle, &bytesAvailable)) {
- WINHTTP_SAFE(GetLastError() == ERROR_WINHTTP_TIMEOUT);
- --retryLimit;
- continue;
- }
-
- if (0 == bytesAvailable)
- break;
-
- responseDataSize += bytesAvailable;
- request->responseData = realloc(request->responseData, responseDataSize + 1);
-
- writePtr = request->responseData + responseDataSize - bytesAvailable;
- writePtr[bytesAvailable] = 0;
-
- bytesLeft = bytesAvailable;
- while (bytesLeft > 0)
- {
- DWORD bytesRead = 0;
- if (!WinHttpReadData(handle, writePtr, bytesLeft, &bytesRead))
- {
- WINHTTP_SAFE(GetLastError() == ERROR_WINHTTP_TIMEOUT);
- if (--retryLimit == 0)
- break;
-
- continue;
- }
-
- bytesLeft -= bytesRead;
- writePtr += bytesRead;
- }
-
- if (bytesLeft > 0)
- HttpSetLastError (http, "Maximum retries count exceeded");
- }
-
- complete = true;
-
- HttpSetLastError (http, NULL);
-
-done:
- free(wideQuery);
- return complete;
-}
-
-const char* HttpGetError(http_t http) {
- return http->error;
-}
+/*
+Copyright (c) 2015
+ Michał Cichoń <thedmd@interia.pl>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#include "config.h"
+#include "http.h"
+#include <Windows.h>
+#include <winhttp.h>
+#pragma comment(lib, "winhttp.lib")
+
+struct _http_t {
+ HINTERNET session;
+ HINTERNET connection;
+ wchar_t* endpoint;
+ wchar_t* securePort;
+ wchar_t* autoProxy;
+ wchar_t* proxy;
+ wchar_t* proxyUsername;
+ wchar_t* proxyPassword;
+ char* error;
+};
+
+static char* HttpToString(const wchar_t* wideString, int size);
+static wchar_t* HttpToWideString(const char* string, int size);
+static bool HttpCreateConnection (http_t http);
+static void HttpCloseConnection (http_t http);
+static void HttpSetLastError (http_t http, const char* message);
+static void HttpSetLastErrorFromWinHttp (http_t http);
+static char* HttpFormatWinApiError (DWORD errorCode, HINSTANCE module);
+static char* HttpFormatWinHttpError (DWORD errorCode);
+static void HttpClearProxy (http_t http);
+
+# define WINHTTP_SAFE(condition) do { if (condition) break; HttpSetLastErrorFromWinHttp (http); return false; } while (false)
+# define WINHTTP_SAFE_DONE(condition) do { if (condition) break; HttpSetLastErrorFromWinHttp (http); goto done; } while (false)
+
+static char* HttpToString(const wchar_t* wideString, int size) {
+ int utfSize = WideCharToMultiByte(CP_UTF8, 0, wideString, size, NULL, 0, NULL, NULL);
+ char* utfMessage = malloc(utfSize + 1);
+ if (utfMessage) {
+ utfMessage[utfSize] = 0;
+ WideCharToMultiByte(CP_UTF8, 0, wideString, size, utfMessage, utfSize, NULL, NULL);
+ }
+ return utfMessage;
+}
+
+static wchar_t* HttpToWideString(const char* string, int size) {
+ int wideSize = MultiByteToWideChar(CP_UTF8, 0, string, size, NULL, 0);
+ int wideBytes = (wideSize + 1) * sizeof(wchar_t);
+ wchar_t* wideMessage = malloc(wideBytes);
+ if (wideMessage) {
+ wideMessage[wideSize] = 0;
+ MultiByteToWideChar(CP_UTF8, 0, string, size, wideMessage, wideSize);
+ }
+ return wideMessage;
+}
+
+
+static bool HttpCreateConnection (http_t http) {
+ INTERNET_PORT defaultPort = INTERNET_DEFAULT_PORT;
+
+ HttpCloseConnection (http);
+
+ http->session = WinHttpOpen(
+ L"WinHTTP/1.0",
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+ WINHTTP_SAFE(http->session != NULL);
+
+ WinHttpSetTimeouts(http->session,
+ 60 * 1000, // DNS time-out
+ 60 * 1000, // connect time-out
+ 30 * 1000, // send time-out
+ 30 * 1000); // receive time-out
+
+ http->connection = WinHttpConnect(
+ http->session,
+ http->endpoint,
+ defaultPort,
+ 0);
+ WINHTTP_SAFE(http->connection != NULL);
+
+ return true;
+}
+
+static void HttpCloseConnection (http_t http) {
+ if (http->connection) {
+ WinHttpCloseHandle(http->connection);
+ http->connection = NULL;
+ }
+
+ if (http->session) {
+ WinHttpCloseHandle(http->session);
+ http->session = NULL;
+ }
+}
+
+static void HttpSetLastError (http_t http, const char* message) {
+ free(http->error);
+ http->error = NULL;
+
+ if (message)
+ http->error = strdup(message);
+}
+
+static void HttpSetLastErrorFromWinHttp (http_t http) {
+ free(http->error);
+ http->error = NULL;
+
+ DWORD error = GetLastError();
+ if (error)
+ http->error = HttpFormatWinHttpError(error);
+}
+
+static char* HttpFormatWinApiError (DWORD errorCode, HINSTANCE module) {
+ const int source_flag = module ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM;
+
+ HLOCAL buffer = NULL;
+ int bufferLength = FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | source_flag,
+ (void*)module,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&buffer,
+ 0,
+ NULL);
+
+ if (bufferLength > 0) {
+ char* message;
+
+ wchar_t* wideMessage = (wchar_t*)buffer;
+
+ /* Drop new line from the end. */
+ wchar_t* wideMessageBack = wideMessage + bufferLength - 1;
+ while (wideMessageBack > wideMessage && (*wideMessageBack == '\r' || *wideMessageBack == '\n'))
+ --wideMessageBack;
+
+ message = HttpToString (wideMessage, wideMessageBack - wideMessage + 1);
+
+ LocalFree(buffer);
+
+ return message;
+ }
+ else
+ return NULL;
+}
+
+static char* HttpFormatWinHttpError (DWORD errorCode) {
+ if (errorCode >= WINHTTP_ERROR_BASE && errorCode <= WINHTTP_ERROR_LAST) {
+ HMODULE module = GetModuleHandleW(L"WinHTTP.dll");
+ if (module) {
+ char* message = HttpFormatWinApiError(errorCode, module);
+ if (message)
+ return message;
+ }
+ }
+
+ return HttpFormatWinApiError(errorCode, NULL);
+}
+
+bool HttpInit(http_t* http, const char* endpoint, const char* securePort) {
+ http_t out = malloc(sizeof(struct _http_t));
+ if (!out)
+ return false;
+ memset(out, 0, sizeof(struct _http_t));
+
+ out->endpoint = HttpToWideString(endpoint, -1);
+ out->securePort = HttpToWideString(securePort, -1);
+
+ if (!HttpCreateConnection (out)) {
+ HttpDestroy (out);
+ return false;
+ }
+
+ *http = out;
+ return true;
+}
+
+void HttpDestroy(http_t http) {
+ if (http) {
+ free(http->endpoint);
+ free(http->securePort);
+ http->endpoint = NULL;
+ http->securePort = NULL;
+ HttpCloseConnection (http);
+ HttpClearProxy (http);
+ }
+ free(http);
+}
+
+static void HttpClearProxy (http_t http) {
+ if (http->autoProxy) {
+ free(http->autoProxy);
+ http->autoProxy = NULL;
+ }
+
+ if (http->proxy) {
+ free(http->proxy);
+ http->proxy = NULL;
+ }
+
+ if (http->proxyUsername) {
+ free(http->proxyUsername);
+ http->proxyUsername = NULL;
+ }
+
+ if (http->proxyPassword) {
+ free(http->proxyPassword);
+ http->proxyPassword = NULL;
+ }
+}
+
+bool HttpSetAutoProxy (http_t http, const char* url) {
+ HttpClearProxy (http);
+ if (HttpSetProxy (http, url)) {
+ http->autoProxy = http->proxy;
+ http->proxy = NULL;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool HttpSetProxy (http_t http, const char* url) {
+ URL_COMPONENTS urlComponents;
+ wchar_t* wideUrl = NULL;
+ wchar_t* wideUrl2 = NULL;
+ wchar_t* wideUsername = NULL;
+ wchar_t* widePassword = NULL;
+
+ ZeroMemory(&urlComponents, sizeof(urlComponents));
+ urlComponents.dwStructSize = sizeof(urlComponents);
+ urlComponents.dwUserNameLength = -1;
+ urlComponents.dwPasswordLength = -1;
+
+ wideUrl = HttpToWideString(url, -1);
+ if (WinHttpCrackUrl(wideUrl, wcslen(wideUrl), 0, &urlComponents)) {
+ if (urlComponents.lpszUserName && urlComponents.dwUserNameLength > 0) {
+ wideUsername = wcsdup(urlComponents.lpszUserName);
+ wideUsername[urlComponents.dwUserNameLength] = 0;
+ }
+ if (urlComponents.lpszPassword && urlComponents.dwPasswordLength > 0) {
+ widePassword = wcsdup(urlComponents.lpszPassword);
+ widePassword[urlComponents.dwPasswordLength] = 0;
+ }
+ }
+
+ ZeroMemory(&urlComponents, sizeof(urlComponents));
+ urlComponents.dwStructSize = sizeof(urlComponents);
+ urlComponents.dwHostNameLength = -1;
+ urlComponents.dwUrlPathLength = -1;
+
+ if (!WinHttpCrackUrl(wideUrl, wcslen(wideUrl), 0, &urlComponents)) {
+ free(wideUsername);
+ free(widePassword);
+ return false;
+ }
+
+ if (urlComponents.lpszHostName && urlComponents.dwHostNameLength > 0) {
+ wideUrl2 = wcsdup(urlComponents.lpszHostName);
+ wideUrl2[urlComponents.lpszUrlPath - urlComponents.lpszHostName] = 0;
+ }
+
+ free(wideUrl);
+
+ HttpClearProxy(http);
+ http->proxy = wideUrl2;
+ http->proxyUsername = wideUsername;
+ http->proxyPassword = widePassword;
+ return true;
+}
+
+bool HttpRequest(http_t http, PianoRequest_t * const request) {
+ HINTERNET handle = NULL;
+ wchar_t* wideQuery = NULL;
+ bool requestSent = false;
+ bool complete = false;
+ int retryLimit = 3;
+ size_t responseDataSize;
+
+ wideQuery = HttpToWideString(request->urlPath, -1);
+ WINHTTP_SAFE_DONE(wideQuery != NULL);
+
+ handle = WinHttpOpenRequest(
+ http->connection,
+ L"POST",
+ wideQuery,
+ L"HTTP/1.1",
+ WINHTTP_NO_REFERER,
+ WINHTTP_DEFAULT_ACCEPT_TYPES,
+ request->secure ? WINHTTP_FLAG_SECURE : 0);
+ WINHTTP_SAFE_DONE(handle != NULL);
+
+ if (http->proxy || http->autoProxy) {
+ wchar_t* fullUrl;
+ DWORD fullUrlSize = 0;
+ WINHTTP_PROXY_INFO proxyInfo;
+ bool success;
+
+ if (http->autoProxy) {
+ WINHTTP_AUTOPROXY_OPTIONS proxyOptions = { 0 };
+
+ success = WinHttpQueryOption(request, WINHTTP_OPTION_URL, NULL, &fullUrlSize);
+ WINHTTP_SAFE(!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ fullUrl = calloc(1, fullUrlSize + 1);
+ success = WinHttpQueryOption(request, WINHTTP_OPTION_URL, fullUrl, &fullUrlSize);
+ if (!success) {
+ free(fullUrl);
+ WINHTTP_SAFE(success);
+ }
+
+ proxyOptions.lpszAutoConfigUrl = http->autoProxy;
+ proxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
+
+ if (!(success = WinHttpGetProxyForUrl(http->session, fullUrl, &proxyOptions, &proxyInfo))) {
+ proxyOptions.fAutoLogonIfChallenged = true;
+ success = WinHttpGetProxyForUrl(http->session, fullUrl, &proxyOptions, &proxyInfo);
+ }
+
+ if (!success) {
+ free(fullUrl);
+ WINHTTP_SAFE(success);
+ }
+ }
+ else {
+ proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxyInfo.lpszProxy = http->proxy;
+ proxyInfo.lpszProxyBypass = NULL;
+ }
+
+ WINHTTP_SAFE(WinHttpSetOption(handle,
+ WINHTTP_OPTION_PROXY,
+ &proxyInfo, sizeof(proxyInfo)));
+
+ if (http->proxyUsername && http->proxyPassword) {
+ WINHTTP_SAFE(WinHttpSetCredentials(handle,
+ WINHTTP_AUTH_TARGET_PROXY,
+ WINHTTP_AUTH_SCHEME_BASIC,
+ http->proxyUsername,
+ http->proxyPassword,
+ NULL));
+ }
+ }
+
+ while (retryLimit > 0) {
+ DWORD errorCode, statusCode, statusCodeSize;
+ bool succeeded = false;
+ bool retry = false;
+
+ if (!requestSent) {
+ size_t postDataSize = strlen(request->postData);
+ succeeded = WinHttpSendRequest(handle,
+ WINHTTP_NO_ADDITIONAL_HEADERS,
+ 0,
+ request->postData,
+ postDataSize,
+ postDataSize,
+ 0);
+
+ if (succeeded)
+ requestSent = true;
+ }
+
+ if (requestSent)
+ succeeded = WinHttpReceiveResponse(handle, NULL);
+
+ errorCode = GetLastError();
+
+ statusCode = 0;
+ statusCodeSize = sizeof(statusCode);
+ if (!WinHttpQueryHeaders(handle,
+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) {
+ statusCode = 0;
+ }
+
+ if (succeeded && statusCode == 407) {
+ requestSent = false;
+ retry = true;
+ }
+ else {
+ if (errorCode == ERROR_SUCCESS)
+ break;
+
+ switch (errorCode) {
+ case ERROR_WINHTTP_RESEND_REQUEST:
+ requestSent = false;
+ /* pass trough */
+
+ case ERROR_WINHTTP_NAME_NOT_RESOLVED:
+ case ERROR_WINHTTP_CANNOT_CONNECT:
+ case ERROR_WINHTTP_TIMEOUT:
+ retry = true;
+ break;
+
+ default:
+ HttpSetLastErrorFromWinHttp (http);
+ goto done;
+ }
+ }
+
+ if (retry)
+ --retryLimit;
+ }
+
+ responseDataSize = 0;
+ while (retryLimit > 0)
+ {
+ DWORD bytesLeft;
+ char* writePtr;
+
+ DWORD bytesAvailable = 0;
+ if (!WinHttpQueryDataAvailable(handle, &bytesAvailable)) {
+ WINHTTP_SAFE(GetLastError() == ERROR_WINHTTP_TIMEOUT);
+ --retryLimit;
+ continue;
+ }
+
+ if (0 == bytesAvailable)
+ break;
+
+ responseDataSize += bytesAvailable;
+ request->responseData = realloc(request->responseData, responseDataSize + 1);
+
+ writePtr = request->responseData + responseDataSize - bytesAvailable;
+ writePtr[bytesAvailable] = 0;
+
+ bytesLeft = bytesAvailable;
+ while (bytesLeft > 0)
+ {
+ DWORD bytesRead = 0;
+ if (!WinHttpReadData(handle, writePtr, bytesLeft, &bytesRead))
+ {
+ WINHTTP_SAFE(GetLastError() == ERROR_WINHTTP_TIMEOUT);
+ if (--retryLimit == 0)
+ break;
+
+ continue;
+ }
+
+ bytesLeft -= bytesRead;
+ writePtr += bytesRead;
+ }
+
+ if (bytesLeft > 0)
+ HttpSetLastError (http, "Maximum retries count exceeded");
+ }
+
+ complete = true;
+
+ HttpSetLastError (http, NULL);
+
+done:
+ free(wideQuery);
+ return complete;
+}
+
+const char* HttpGetError(http_t http) {
+ return http->error;
+}
diff --git a/src/http.h b/src/http/http.h
index a321cd3..a321cd3 100644
--- a/src/http.h
+++ b/src/http/http.h
diff --git a/src/libpiano/response.c b/src/libpiano/response.c
index 783e2f0..8b6ed52 100644
--- a/src/libpiano/response.c
+++ b/src/libpiano/response.c
@@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-#include "../config.h"
+#include "config.h"
#include <json/json.h>
#include <string.h>
diff --git a/src/main.c b/src/main.c
index 900e56e..99793f3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -337,11 +337,18 @@ int main (int argc, char **argv) {
BarConsoleSetTitle (TITLE);
/* init some things */
- BarPlayer2Init (&app.player);
-
BarSettingsInit (&app.settings);
BarSettingsRead (&app.settings);
+ if (!BarPlayer2Init (&app.player, app.settings.player))
+ {
+ if (app.settings.player)
+ BarUiMsg(&app.settings, MSG_ERR, "Player \"%s\" initialization failed.", app.settings.player);
+ else
+ BarUiMsg(&app.settings, MSG_ERR, "Player initialization failed.");
+ return 0;
+ }
+
PianoReturn_t pret;
if ((pret = PianoInit (&app.ph, app.settings.partnerUser,
app.settings.partnerPassword, app.settings.device,
diff --git a/src/main.h b/src/main.h
index f16a168..6c34d71 100644
--- a/src/main.h
+++ b/src/main.h
@@ -28,8 +28,8 @@ THE SOFTWARE.
#include <piano.h>
-#include "player2.h"
-#include "http.h"
+#include "player/player2.h"
+#include "http/http.h"
#include "settings.h"
#include "ui_readline.h"
diff --git a/src/player/backends/direct_show.c b/src/player/backends/direct_show.c
new file mode 100644
index 0000000..111f042
--- /dev/null
+++ b/src/player/backends/direct_show.c
@@ -0,0 +1,464 @@
+/*
+Copyright (c) 2015
+Micha³ Cichoñ <thedmd@interia.pl>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/* receive/play audio stream */
+
+/* based on DShow example player */
+
+#include "config.h"
+#include "../player2_private.h"
+#define COBJMACROS
+#define INITGUID
+#include <objbase.h>
+#include <dshow.h>
+#pragma comment(lib, "strmiids.lib")
+
+# define WM_GRAPH_EVENT (WM_APP + 1)
+
+enum { NO_GRAPH, RUNNING, PAUSED, STOPPED };
+
+static struct _player_static_t
+{
+ bool done;
+ bool initialized;
+ bool hasCOM;
+} BarPlayerGlobal = { 0 };
+
+struct _player_t
+{
+ int state;
+ IGraphBuilder* graph;
+ IMediaControl* control;
+ IMediaEventEx* event;
+ IBasicAudio* audio;
+ IMediaSeeking* media;
+ float volume; // dB
+ float gain; // dB
+};
+
+static bool DSPlayerStaticInit();
+static void DSPlayerStaticTerm(void);
+
+static bool DSPlayerStaticInit()
+{
+ if (BarPlayerGlobal.done)
+ return BarPlayerGlobal.initialized;
+
+ BarPlayerGlobal.done = true;
+
+ atexit(DSPlayerStaticTerm);
+
+ if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
+ return false;
+
+ BarPlayerGlobal.hasCOM = true;
+
+ BarPlayerGlobal.initialized = true;
+
+ return true;
+}
+
+static void DSPlayerStaticTerm(void)
+{
+ if (!BarPlayerGlobal.done)
+ return;
+
+ if (BarPlayerGlobal.hasCOM)
+ {
+ CoUninitialize();
+ BarPlayerGlobal.hasCOM = false;
+ }
+
+ BarPlayerGlobal.initialized = false;
+ BarPlayerGlobal.done = false;
+}
+
+static void DSPlayerApplyVolume(player2_t player)
+{
+ long v = (long)((player->volume + player->gain) * 100.0f);
+
+ if (!player->audio)
+ return;
+
+ if (v < -10000)
+ v = -10000;
+ if (v > 0)
+ v = 0;
+
+ IBasicAudio_put_Volume(player->audio, v);
+}
+
+static void DSPlayerTearDown(player2_t player)
+{
+ /* TODO: send final event */
+
+ if (player->graph)
+ {
+ IGraphBuilder_Release(player->graph);
+ player->graph = NULL;
+ }
+
+ if (player->control)
+ {
+ IMediaControl_Release(player->control);
+ player->control = NULL;
+ }
+
+ if (player->event)
+ {
+ IMediaEventEx_Release(player->event);
+ player->event = NULL;
+ }
+
+ if (player->audio)
+ {
+ IBasicAudio_Release(player->audio);
+ player->audio = NULL;
+ }
+
+ if (player->media)
+ {
+ IMediaSeeking_Release(player->media);
+ player->media = NULL;
+ }
+
+ player->state = NO_GRAPH;
+}
+
+static HRESULT DSPlayerBuild(player2_t player)
+{
+ HRESULT hr;
+
+ DSPlayerTearDown(player);
+
+ hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, &player->graph);
+ if (FAILED(hr))
+ return hr;
+
+ hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaControl, &player->control);
+ if (FAILED(hr))
+ return hr;
+
+ hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaEventEx, &player->event);
+ if (FAILED(hr))
+ return hr;
+
+ hr = IGraphBuilder_QueryInterface(player->graph, &IID_IBasicAudio, &player->audio);
+ if (FAILED(hr))
+ return hr;
+
+ hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaSeeking, &player->media);
+ if (FAILED(hr))
+ return hr;
+
+ hr = IMediaEventEx_SetNotifyWindow(player->event, (OAHWND)NULL, WM_GRAPH_EVENT, (LONG_PTR)player);
+ if (FAILED(hr))
+ return hr;
+
+ player->state = STOPPED;
+
+ return S_OK;
+}
+
+static HRESULT DSPlayerAddFilterByCLSID(IGraphBuilder *pGraph, REFGUID clsid, IBaseFilter **ppF, LPCWSTR wszName)
+{
+ IBaseFilter *pFilter = NULL;
+ HRESULT hr;
+ *ppF = 0;
+
+ hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IBaseFilter, &pFilter);
+ if (FAILED(hr))
+ goto done;
+
+ hr = IGraphBuilder_AddFilter(pGraph, pFilter, wszName);
+ if (FAILED(hr))
+ goto done;
+
+ *ppF = pFilter;
+
+ IBaseFilter_AddRef(*ppF);
+
+done:
+ if (pFilter)
+ IBaseFilter_Release(pFilter);
+
+ return hr;
+}
+
+static HRESULT DSPlayerRender(player2_t player, IBaseFilter* source)
+{
+ BOOL bRenderedAnyPin = FALSE;
+
+ IPin* pin = NULL;
+ IEnumPins *enumPins = NULL;
+ IBaseFilter *audioRenderer = NULL;
+ IFilterGraph2* filter = NULL;
+
+ HRESULT hr;
+
+ hr = IGraphBuilder_QueryInterface(player->graph, &IID_IFilterGraph2, &filter);
+ if (FAILED(hr))
+ return hr;
+
+ hr = DSPlayerAddFilterByCLSID(player->graph, &CLSID_DSoundRender, &audioRenderer, L"Audio Renderer");
+ if (FAILED(hr))
+ goto done;
+
+ hr = IBaseFilter_EnumPins(source, &enumPins);
+ if (FAILED(hr))
+ goto done;
+
+ while (S_OK == IEnumPins_Next(enumPins, 1, &pin, NULL))
+ {
+ HRESULT hr2 = IFilterGraph2_RenderEx(filter, pin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
+
+ IPin_Release(pin);
+ if (SUCCEEDED(hr2))
+ bRenderedAnyPin = TRUE;
+ }
+
+done:
+ if (enumPins)
+ IEnumPins_Release(enumPins);
+ if (enumPins)
+ IBaseFilter_Release(audioRenderer);
+ if (enumPins)
+ IFilterGraph2_Release(filter);
+
+ if (SUCCEEDED(hr) && !bRenderedAnyPin)
+ hr = VFW_E_CANNOT_RENDER;
+
+ return hr;
+}
+
+static player2_t DSPlayerCreate()
+{
+ player2_t out = NULL;
+
+ if (!DSPlayerStaticInit())
+ return NULL;
+
+ out = malloc(sizeof(struct _player_t));
+ if (!out)
+ return NULL;
+
+ memset(out, 0, sizeof(struct _player_t));
+
+ return out;
+}
+
+static void DSPlayerDestroy(player2_t player)
+{
+ DSPlayerTearDown(player);
+ free(player);
+}
+
+static void DSPlayerSetVolume(player2_t player, float volume)
+{
+ player->volume = volume;
+ DSPlayerApplyVolume(player);
+}
+
+static float DSPlayerGetVolume(player2_t player)
+{
+ return player->volume;
+}
+
+static void DSPlayerSetGain(player2_t player, float gain)
+{
+ player->gain = gain;
+ DSPlayerApplyVolume(player);
+}
+
+static float DSPlayerGetGain(player2_t player)
+{
+ return player->gain;
+}
+
+static double DSPlayerGetDuration(player2_t player)
+{
+ LONGLONG time;
+ if (SUCCEEDED(IMediaSeeking_GetDuration(player->media, &time)))
+ return time / 10000000.0;
+ else
+ return 0;
+}
+
+static double DSPlayerGetTime(player2_t player)
+{
+ LONGLONG time;
+ if (SUCCEEDED(IMediaSeeking_GetCurrentPosition(player->media, &time)))
+ return time / 10000000.0;
+ else
+ return 0;
+}
+
+static bool DSPlayerOpen(player2_t player, const char* url)
+{
+ IBaseFilter* source = NULL;
+ HRESULT hr;
+ wchar_t* wideUrl = NULL;
+ size_t urlSize;
+ int result;
+
+ hr = DSPlayerBuild(player);
+ if (FAILED(hr))
+ goto done;
+
+ urlSize = strlen(url);
+ result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, NULL, 0);
+ wideUrl = malloc((result + 1) * sizeof(wchar_t));
+ if (!wideUrl)
+ {
+ hr = E_OUTOFMEMORY;
+ goto done;
+ }
+ memset(wideUrl, 0, (result + 1) * sizeof(wchar_t));
+
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, wideUrl, result);
+
+ hr = IGraphBuilder_AddSourceFilter(player->graph, wideUrl, NULL, &source);
+ if (FAILED(hr))
+ goto done;
+
+ hr = DSPlayerRender(player, source);
+
+ DSPlayerApplyVolume(player);
+
+done:
+ if (wideUrl)
+ free(wideUrl);
+ if (FAILED(hr))
+ DSPlayerTearDown(player);
+ if (source)
+ IBaseFilter_Release(source);
+
+ return SUCCEEDED(hr);
+}
+
+static bool DSPlayerPlay(player2_t player)
+{
+ HRESULT hr;
+
+ if (player->state != PAUSED && player->state != STOPPED)
+ return false; /* wrong state */
+
+ hr = IMediaControl_Run(player->control);
+ if (SUCCEEDED(hr))
+ player->state = RUNNING;
+
+ return SUCCEEDED(hr);
+}
+
+static bool DSPlayerPause(player2_t player)
+{
+ HRESULT hr;
+
+ if (player->state != RUNNING)
+ return false; /* wrong state */
+
+ hr = IMediaControl_Pause(player->control);
+ if (SUCCEEDED(hr))
+ player->state = PAUSED;
+
+ return SUCCEEDED(hr);
+}
+
+static bool DSPlayerStop(player2_t player)
+{
+ HRESULT hr;
+
+ if (player->state != RUNNING && player->state != PAUSED)
+ return false; /* wrong state */
+
+ hr = IMediaControl_Stop(player->control);
+ if (SUCCEEDED(hr))
+ player->state = STOPPED;
+
+ return SUCCEEDED(hr);
+}
+
+static bool DSPlayerFinish(player2_t player)
+{
+ if (!player->control)
+ return false;
+
+ DSPlayerTearDown(player);
+ return true;
+}
+
+static bool DSPlayerIsPlaying(player2_t player)
+{
+ return player->state == RUNNING;
+}
+
+static bool DSPlayerIsPaused(player2_t player)
+{
+ return player->state == PAUSED;
+}
+
+static bool DSPlayerIsStopped(player2_t player)
+{
+ return player->state == STOPPED;
+}
+
+static bool DSPlayerIsFinished(player2_t player)
+{
+ LONGLONG time;
+ LONGLONG duration;
+
+ if (!player->media || player->state == NO_GRAPH)
+ return true;
+
+ if (player->state != RUNNING && player->state != STOPPED)
+ return false;
+
+ if (FAILED(IMediaSeeking_GetDuration(player->media, &duration)) ||
+ FAILED(IMediaSeeking_GetCurrentPosition(player->media, &time)))
+ return true;
+
+ return time >= duration;
+}
+
+player2_iface player2_direct_show =
+{
+ .Id = "ds",
+ .Name = "Direct Show",
+ .Create = DSPlayerCreate,
+ .Destroy = DSPlayerDestroy,
+ .SetVolume = DSPlayerSetVolume,
+ .GetVolume = DSPlayerGetVolume,
+ .SetGain = DSPlayerSetGain,
+ .GetGain = DSPlayerGetGain,
+ .GetDuration = DSPlayerGetDuration,
+ .GetTime = DSPlayerGetTime,
+ .Open = DSPlayerOpen,
+ .Play = DSPlayerPlay,
+ .Pause = DSPlayerPause,
+ .Stop = DSPlayerStop,
+ .Finish = DSPlayerFinish,
+ .IsPlaying = DSPlayerIsPlaying,
+ .IsPaused = DSPlayerIsPaused,
+ .IsStopped = DSPlayerIsStopped,
+ .IsFinished = DSPlayerIsFinished
+};
diff --git a/src/player/backends/media_foundation.cpp b/src/player/backends/media_foundation.cpp
new file mode 100644
index 0000000..6aa0d00
--- /dev/null
+++ b/src/player/backends/media_foundation.cpp
@@ -0,0 +1,1110 @@
+/*
+Copyright (c) 2015
+Micha³ Cichoñ <thedmd@interia.pl>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/* receive/play audio stream */
+
+/* based on DShow example player */
+
+extern "C" {
+# include "config.h"
+# include "../player2_private.h"
+}
+#undef restrict
+#undef inline
+# include "media_foundation.h"
+# include <mferror.h>
+# include <cassert>
+# include <cmath>
+
+//# pragma comment(lib, "mf.lib")
+//# pragma comment(lib, "mfplat.lib")
+# pragma comment(lib, "mfuuid.lib")
+
+// MFPlat.dll
+typedef HRESULT (__stdcall *MFSTARTUPPROC)(ULONG Version, DWORD dwFlags/* = MFSTARTUP_FULL*/);
+typedef HRESULT (__stdcall *MFSHUTDOWNPROC)();
+typedef HRESULT (__stdcall *MFCREATESOURCERESOLVERPROC)(IMFSourceResolver** ppISourceResolver);
+
+// MF.dll
+typedef HRESULT (__stdcall *MFGETSERVICEPROC)(IUnknown* punkObject, REFGUID guidService, REFIID riid, LPVOID* ppvObject);
+typedef HRESULT (__stdcall *MFCREATETOPOLOGYPROC)(IMFTopology** ppTopo);
+typedef HRESULT (__stdcall *MFCREATETOPOLOGYNODEPROC)(MF_TOPOLOGY_TYPE NodeType, IMFTopologyNode** ppNode);
+typedef HRESULT (__stdcall *MFCREATEAUDIORENDERERACTIVATEPROC)(IMFActivate** ppActivate);
+typedef HRESULT (__stdcall *MFCREATEMEDIASESSIONPROC)(IMFAttributes* pConfiguration, IMFMediaSession** ppMediaSession);
+
+struct MF
+{
+ bool Loaded;
+ bool Tried;
+ HMODULE MFModule;
+ HMODULE MFPlatModule;
+
+ // MFPlat.dll
+ MFSTARTUPPROC MFStartup;
+ MFSHUTDOWNPROC MFShutdown;
+ MFCREATESOURCERESOLVERPROC MFCreateSourceResolver;
+
+ // MF.dll
+ MFGETSERVICEPROC MFGetService;
+ MFCREATETOPOLOGYPROC MFCreateTopology;
+ MFCREATETOPOLOGYNODEPROC MFCreateTopologyNode;
+ MFCREATEAUDIORENDERERACTIVATEPROC MFCreateAudioRendererActivate;
+ MFCREATEMEDIASESSIONPROC MFCreateMediaSession;
+};
+static_assert(std::is_pod<MF>::value, "");
+
+static MF g_MF = { 0 };
+
+static void MFUnload()
+{
+ if (!g_MF.Tried)
+ return;
+
+ if (g_MF.MFModule)
+ {
+ FreeLibrary(g_MF.MFModule);
+ g_MF.MFModule = nullptr;
+ }
+
+ if (g_MF.MFPlatModule)
+ {
+ FreeLibrary(g_MF.MFPlatModule);
+ g_MF.MFPlatModule = nullptr;
+ }
+
+ g_MF.Loaded = false;
+}
+
+static bool MFLoad()
+{
+ if (g_MF.Tried)
+ return g_MF.Loaded;
+
+ g_MF.Tried = true;
+
+ g_MF.MFPlatModule = LoadLibrary(L"MFPlat.dll");
+ g_MF.MFModule = LoadLibrary(L"MF.dll");
+
+ if (!g_MF.MFPlatModule || !g_MF.MFModule)
+ {
+ MFUnload();
+ return false;
+ }
+
+ bool success = true;
+# define GET_PROC(module, type, name) success &= (g_MF.name = reinterpret_cast<type>(GetProcAddress(module, #name))) != nullptr
+
+ // MFPlat.dll
+ GET_PROC(g_MF.MFPlatModule, MFSTARTUPPROC, MFStartup);
+ GET_PROC(g_MF.MFPlatModule, MFSHUTDOWNPROC, MFShutdown);
+ GET_PROC(g_MF.MFPlatModule, MFCREATESOURCERESOLVERPROC, MFCreateSourceResolver);
+
+ // MF.dll
+ GET_PROC(g_MF.MFModule, MFGETSERVICEPROC, MFGetService);
+ GET_PROC(g_MF.MFModule, MFCREATETOPOLOGYPROC, MFCreateTopology);
+ GET_PROC(g_MF.MFModule, MFCREATETOPOLOGYNODEPROC, MFCreateTopologyNode);
+ GET_PROC(g_MF.MFModule, MFCREATEAUDIORENDERERACTIVATEPROC, MFCreateAudioRendererActivate);
+ GET_PROC(g_MF.MFModule, MFCREATEMEDIASESSIONPROC, MFCreateMediaSession);
+
+ if (!success)
+ {
+ MFUnload();
+ return false;
+ }
+
+ g_MF.Loaded = true;
+
+ atexit(MFUnload);
+
+ return true;
+}
+
+# define SAFE_CALL(expression) do { hr = expression; if (FAILED(hr)) return hr; } while (false)
+
+// Placement new is missing if we do not include <memory>
+//static inline void* operator new(size_t size, void* ptr)
+//{
+// return ptr;
+//}
+
+template <class Q>
+static HRESULT GetEventObject(IMFMediaEvent *mediaEvent, Q** outObject)
+{
+ *outObject = nullptr;
+
+ PROPVARIANT var;
+ HRESULT hr = mediaEvent->GetValue(&var);
+ if (SUCCEEDED(hr))
+ {
+ if (var.vt == VT_UNKNOWN)
+ hr = var.punkVal->QueryInterface(outObject);
+ else
+ hr = MF_E_INVALIDTYPE;
+
+ PropVariantClear(&var);
+ }
+ return hr;
+}
+
+//static HRESULT CreateMediaSource(PCWSTR pszURL, IMFMediaSource **ppSource);
+static HRESULT CreatePlaybackTopology(IMFMediaSource *pSource, IMFPresentationDescriptor *pPD, IMFTopology **ppTopology);
+static float MFTIMEToSeconds(MFTIME time)
+{
+ float seconds = (float)(time / 1e7);
+ return seconds;
+}
+
+static void DefaultMediaPlayerEventCallback(MediaPlayer* player, com_ptr<IMFMediaEvent> mediaEvent, MediaEventType eventType)
+{
+ player->HandleEvent(mediaEvent);
+}
+
+HRESULT MediaPlayer::Create(MediaPlayerEventCallback eventCallback, MediaPlayer** outMediaPlayer)
+{
+ if (!outMediaPlayer)
+ return E_POINTER;
+
+ if (!eventCallback)
+ eventCallback = DefaultMediaPlayerEventCallback;
+
+ // auto mediaPlayer = new (std::nothrow) MediaPlayer(eventCallback);
+
+ // Cheep object construction without throwing exceptions around.
+ auto mediaPlayer = reinterpret_cast<MediaPlayer*>(malloc(sizeof(MediaPlayer)));
+ if (!mediaPlayer)
+ return E_OUTOFMEMORY;
+
+ new (mediaPlayer) MediaPlayer(eventCallback);
+
+ auto hr = mediaPlayer->Initialize();
+ if (SUCCEEDED(hr))
+ *outMediaPlayer = mediaPlayer;
+ else
+ mediaPlayer->Release();
+
+ return hr;
+}
+
+MediaPlayer::MediaPlayer(MediaPlayerEventCallback eventCallback):
+ m_RefCount(1),
+ m_SourceResolver(nullptr),
+ m_CancelCookie(nullptr),
+ m_MediaSession(nullptr),
+ m_MediaSource(nullptr),
+ m_SimpleAudioVolume(nullptr),
+ m_PresentationClock(nullptr),
+ m_StreamAudioVolume(nullptr),
+ m_SetMasterVolume(),
+ m_MasterVolume(1.0f),
+ m_ReplayGain(0.0f),
+ m_EventCallback(eventCallback),
+ m_UserData(nullptr),
+ m_State(Closed),
+ m_CloseEvent(nullptr)
+{
+}
+
+MediaPlayer::~MediaPlayer()
+{
+ // If FALSE, the app did not call Shutdown().
+ assert(m_MediaSession == nullptr);
+
+ // When MediaPlayer calls IMediaEventGenerator::BeginGetEvent on the
+ // media session, it causes the media session to hold a reference
+ // count on the MediaPlayer.
+
+ // This creates a circular reference count between MediaPlayer and the
+ // media session. Calling Shutdown breaks the circular reference
+ // count.
+
+ // If CreateInstance fails, the application will not call
+ // Shutdown. To handle that case, call Shutdown in the destructor.
+ Shutdown();
+}
+
+
+ULONG STDMETHODCALLTYPE MediaPlayer::AddRef()
+{
+ return InterlockedIncrement(&m_RefCount);
+}
+
+ULONG STDMETHODCALLTYPE MediaPlayer::Release()
+{
+ auto count = InterlockedDecrement(&m_RefCount);
+ if (count == 0)
+ {
+ // Do manually deleter job, since malloc was used to allocate memory
+ this->~MediaPlayer();
+ free(this);
+
+ // delete this;
+ }
+ return count;
+}
+
+HRESULT STDMETHODCALLTYPE MediaPlayer::QueryInterface(REFIID iid, void** object)
+{
+ if (!object)
+ return E_POINTER;
+
+ *object = nullptr;
+ if (iid == IID_IUnknown)
+ *object = static_cast<IUnknown*>(this);
+ else if (iid == IID_IMFAsyncCallback)
+ *object = static_cast<IMFAsyncCallback*>(this);
+ else
+ return E_NOINTERFACE;
+
+ AddRef();
+
+ return S_OK;
+}
+
+HRESULT MediaPlayer::Initialize()
+{
+ auto hr = g_MF.MFStartup(MF_VERSION, MFSTARTUP_FULL);
+ if (FAILED(hr))
+ return hr;
+
+ m_CloseEvent = CreateEvent(nullptr, false, false, nullptr);
+ if (!m_CloseEvent)
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ return hr;
+}
+
+HRESULT MediaPlayer::Shutdown()
+{
+ auto hr = CloseSession();
+
+ g_MF.MFShutdown();
+
+ if (m_CloseEvent)
+ {
+ CloseHandle(m_CloseEvent);
+ m_CloseEvent = nullptr;
+ }
+
+ return hr;
+}
+
+HRESULT MediaPlayer::CreateSession()
+{
+ HRESULT hr = S_OK;
+
+ SAFE_CALL(CloseSession());
+ assert(m_State == Closed);
+ SAFE_CALL(g_MF.MFCreateMediaSession(nullptr, &m_MediaSession));
+ SAFE_CALL(m_MediaSession->BeginGetEvent(this, nullptr));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::CloseSession()
+{
+ // The IMFMediaSession::Close method is asynchronous, but the
+ // CPlayer::CloseSession method waits on the MESessionClosed mediaEvent.
+ //
+ // MESessionClosed is guaranteed to be the last mediaEvent that the
+ // media session fires.
+
+ HRESULT hr = S_OK;
+
+ if (m_SourceResolver)
+ {
+ if (m_CancelCookie)
+ {
+ SAFE_CALL(m_SourceResolver->CancelObjectCreation(m_CancelCookie));
+ m_CancelCookie = nullptr;
+ }
+
+ m_SourceResolver = nullptr;
+ }
+
+ if (m_MediaSession)
+ {
+ SAFE_CALL(SetState(Closing));
+
+ hr = m_MediaSession->Close();
+ if (SUCCEEDED(hr))
+ {
+ auto waitResult = WaitForSingleObject(m_CloseEvent, 5000);
+ assert(waitResult != WAIT_TIMEOUT);
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Shut down the media source. (Synchronous operation, no events.)
+ if (m_MediaSource)
+ m_MediaSource->Shutdown();
+
+ // Shut down the media session. (Synchronous operation, no events.)
+ if (m_MediaSession)
+ m_MediaSession->Shutdown();
+ }
+
+ m_MediaSource = nullptr;
+ m_MediaSession = nullptr;
+
+ SAFE_CALL(SetState(Closed));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::OpenURL(const wchar_t* url)
+{
+ // 1. Create a new media session.
+ // 2. Create the media source.
+ // 3. Create the topology.
+ // 4. Queue the topology [asynchronous]
+ // 5. Start playback [asynchronous - does not happen in this method.]
+
+ auto doOpenAsync = [this](const wchar_t* url)
+ {
+ HRESULT hr = S_OK;
+
+ SAFE_CALL(CreateSession());
+
+ SAFE_CALL(g_MF.MFCreateSourceResolver(&m_SourceResolver));
+
+ SAFE_CALL(m_SourceResolver->BeginCreateObjectFromURL(
+ url, // URL of the source.
+ MF_RESOLUTION_MEDIASOURCE, // Create a source object.
+ NULL, // Optional property store.
+ &m_CancelCookie, // Receives cookie for cancelation.
+ this, // Callback for recieving events.
+ nullptr)); // User defined state.
+
+ return hr;
+ };
+
+ auto hr = doOpenAsync(url);
+ if (SUCCEEDED(hr))
+ SAFE_CALL(SetState(OpenPending));
+ else
+ SAFE_CALL(SetState(Closed));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::ApplyTopology()
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFTopology> topology;
+ com_ptr<IMFPresentationDescriptor> presentationDescriptor;
+
+ SAFE_CALL(m_MediaSource->CreatePresentationDescriptor(&presentationDescriptor));
+ SAFE_CALL(CreatePlaybackTopology(m_MediaSource, presentationDescriptor, &topology));
+ SAFE_CALL(m_MediaSession->SetTopology(0, topology));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::StartPlayback()
+{
+ assert(m_MediaSession);
+
+ PROPVARIANT start;
+ PropVariantInit(&start);
+
+ if (m_State == Stopped)
+ {
+ // After stop, always play from beginning
+ start.vt = VT_I8;
+ start.uhVal.QuadPart = 0;
+ }
+
+ auto hr = m_MediaSession->Start(nullptr, &start);
+ if (SUCCEEDED(hr))
+ {
+ // Note: Start is an asynchronous operation. However, we
+ // can treat our state as being already started. If Start
+ // fails later, we'll get an MESessionStarted mediaEvent with
+ // an error code, and we will update our state then.
+ SAFE_CALL(SetState(Started));
+ }
+ PropVariantClear(&start);
+
+ return hr;
+}
+
+HRESULT MediaPlayer::Play()
+{
+ if (m_State != Paused && m_State != Stopped)
+ return MF_E_INVALIDREQUEST;
+
+ if (!m_MediaSession || !m_MediaSource)
+ return E_UNEXPECTED;
+
+ return StartPlayback();
+}
+
+HRESULT MediaPlayer::Pause()
+{
+ if (m_State != Started)
+ return MF_E_INVALIDREQUEST;
+
+ if (!m_MediaSession || !m_MediaSource)
+ return E_UNEXPECTED;
+
+ auto hr = m_MediaSession->Pause();
+ if (SUCCEEDED(hr))
+ SAFE_CALL(SetState(Paused));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::Stop()
+{
+ if (m_State != Started && m_State != Paused)
+ return MF_E_INVALIDREQUEST;
+
+ if (!m_MediaSession)
+ return E_UNEXPECTED;
+
+ auto hr = m_MediaSession->Stop();
+ if (SUCCEEDED(hr))
+ SAFE_CALL(SetState(Stopped));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::SetState(State state)
+{
+ if (m_State == state)
+ return S_OK;
+
+ auto previousState = m_State;
+
+ m_State = state;
+
+ HRESULT hr = S_OK;
+ SAFE_CALL(OnStateChange(m_State, previousState));
+ return hr;
+}
+
+MediaPlayer::State MediaPlayer::GetState() const
+{
+ return m_State;
+}
+
+void MediaPlayer::SetMasterVolume(float volume)
+{
+ m_MasterVolume = min(1.0f, max(0.0f, volume));
+ if (FAILED(ApplyMasterVolume(volume)))
+ m_SetMasterVolume = volume;
+}
+
+HRESULT MediaPlayer::ApplyMasterVolume(float volume)
+{
+ if (m_SimpleAudioVolume)
+ return m_SimpleAudioVolume->SetMasterVolume(volume);
+ else
+ return E_FAIL;
+}
+
+float MediaPlayer::GetMasterVolume() const
+{
+ if (m_SimpleAudioVolume)
+ {
+ float masterVolume = 1.0f;
+ if (SUCCEEDED(m_SimpleAudioVolume->GetMasterVolume(&masterVolume)))
+ m_MasterVolume = masterVolume;
+ }
+
+ return m_MasterVolume;
+}
+
+
+void MediaPlayer::SetReplayGain(float replayGain)
+{
+ m_ReplayGain = replayGain;
+ ApplyReplayGain(replayGain);
+}
+
+HRESULT MediaPlayer::ApplyReplayGain(float replayGain)
+{
+ if (m_StreamAudioVolume)
+ {
+ HRESULT hr = S_OK;
+
+ // Attenuation (dB) = 20 * log10(Level)
+ const float attenuation = powf(10.0f, replayGain / 20.0f);
+ const float volume = max(0.0f, min(1.0f, attenuation));
+
+ // This is poor-man method since positive gain is always clipped.
+ // To fully implement replay gain use custom MFT to process
+ // audio data.
+ UINT32 channelCount = 0;
+ SAFE_CALL(m_StreamAudioVolume->GetChannelCount(&channelCount));
+
+ for (UINT32 i = 0; i < channelCount; ++i)
+ hr = m_StreamAudioVolume->SetChannelVolume(i, volume);
+
+ return hr;
+ }
+ else
+ return E_FAIL;
+}
+
+float MediaPlayer::GetReplayGain() const
+{
+ return m_ReplayGain;
+}
+
+optional<float> MediaPlayer::GetPresentationTime() const
+{
+ if (m_PresentationClock)
+ {
+ MFTIME presentationTime = 0;
+ if (SUCCEEDED(m_PresentationClock->GetTime(&presentationTime)))
+ return MFTIMEToSeconds(presentationTime);
+ }
+
+ return nullopt;
+}
+
+optional<float> MediaPlayer::GetDuration() const
+{
+ if (m_MediaSource)
+ {
+ com_ptr<IMFPresentationDescriptor> presentationDescriptor;
+
+ if (SUCCEEDED(m_MediaSource->CreatePresentationDescriptor(&presentationDescriptor)))
+ {
+ MFTIME durationTime = 0;
+ if (SUCCEEDED(presentationDescriptor->GetUINT64(MF_PD_DURATION, reinterpret_cast<UINT64*>(&durationTime))))
+ return MFTIMEToSeconds(durationTime);
+ }
+ }
+
+ return nullopt;
+}
+
+void MediaPlayer::SetUserData(void* userData)
+{
+ m_UserData = userData;
+}
+
+void* MediaPlayer::GetUserData() const
+{
+ return m_UserData;
+}
+
+
+HRESULT MediaPlayer::HandleEvent(IMFMediaEvent* mediaEvent)
+{
+ if (!mediaEvent)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ HRESULT hrStatus = S_OK;
+
+ MediaEventType eventType = MEUnknown;
+ SAFE_CALL(mediaEvent->GetType(&eventType));
+ SAFE_CALL(mediaEvent->GetStatus(&hrStatus));
+ SAFE_CALL(hrStatus);
+
+ switch (eventType)
+ {
+ case MESessionTopologyStatus: SAFE_CALL(OnTopologyStatus(mediaEvent)); break;
+ case MEEndOfPresentation: SAFE_CALL(OnPresentationEnded(mediaEvent)); break;
+ case MENewPresentation: SAFE_CALL(OnNewPresentation(mediaEvent)); break;
+ default: break;
+ }
+
+ SAFE_CALL(OnSessionEvent(mediaEvent, eventType));
+
+ return hr;
+}
+
+HRESULT MediaPlayer::OnTopologyStatus(IMFMediaEvent* mediaEvent)
+{
+ UINT32 status = 0;
+ HRESULT hr = S_OK;
+
+ SAFE_CALL(mediaEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status));
+ if (status == MF_TOPOSTATUS_READY)
+ SAFE_CALL(StartPlayback());
+
+ return hr;
+}
+
+HRESULT MediaPlayer::OnPresentationEnded(IMFMediaEvent* mediaEvent)
+{
+ HRESULT hr = S_OK;
+ SAFE_CALL(SetState(Stopped));
+ return hr;
+}
+
+HRESULT MediaPlayer::OnNewPresentation(IMFMediaEvent* mediaEvent)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFTopology> topology;
+ com_ptr<IMFPresentationDescriptor> presentationDescriptor;
+
+ SAFE_CALL(GetEventObject(mediaEvent, &presentationDescriptor));
+ SAFE_CALL(CreatePlaybackTopology(m_MediaSource, presentationDescriptor, &topology));
+ SAFE_CALL(m_MediaSession->SetTopology(0, topology));
+
+ SAFE_CALL(SetState(OpenPending));
+
+ return S_OK;
+}
+
+HRESULT MediaPlayer::OnSessionEvent(IMFMediaEvent*, MediaEventType)
+{
+ return S_OK;
+}
+
+HRESULT MediaPlayer::OnStateChange(State state, State previousState)
+{
+ HRESULT hr = S_OK;
+
+ if (state == Started && previousState != Paused)
+ {
+ m_SimpleAudioVolume = nullptr;
+ m_PresentationClock = nullptr;
+
+ com_ptr<IMFClock> clock;
+ SAFE_CALL(m_MediaSession->GetClock(&clock));
+ SAFE_CALL(clock->QueryInterface(&m_PresentationClock));
+
+ SAFE_CALL(g_MF.MFGetService(m_MediaSession, MR_POLICY_VOLUME_SERVICE, IID_IMFSimpleAudioVolume, (void**)&m_SimpleAudioVolume));
+
+ // Set master volume to previously set value
+ if (m_SetMasterVolume)
+ {
+ SAFE_CALL(ApplyMasterVolume(*m_SetMasterVolume));
+ m_SetMasterVolume = nullopt;
+ }
+
+ g_MF.MFGetService(m_MediaSession, MR_STREAM_VOLUME_SERVICE, IID_IMFAudioStreamVolume, (void**)&m_StreamAudioVolume);
+ ApplyReplayGain(m_ReplayGain);
+ }
+
+ if (state == Stopped)
+ {
+ m_StreamAudioVolume = nullptr;
+ m_SimpleAudioVolume = nullptr;
+ m_PresentationClock = nullptr;
+ }
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE MediaPlayer::GetParameters(DWORD* flags, DWORD* queue)
+{
+ return E_NOTIMPL; // optional, not implemented
+}
+
+HRESULT STDMETHODCALLTYPE MediaPlayer::Invoke(IMFAsyncResult* asyncResult)
+{
+ HRESULT hr = S_OK;
+
+ if (m_SourceResolver && m_State == OpenPending)
+ {
+ auto doFinishOpen = [this](IMFAsyncResult* asyncResult)
+ {
+ HRESULT hr = S_OK;
+
+ // We are in async OpenURL
+ MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
+ com_ptr<IUnknown> source;
+
+ SAFE_CALL(m_SourceResolver->EndCreateObjectFromURL(asyncResult, &objectType, &source));
+ SAFE_CALL(source->QueryInterface(&m_MediaSource));
+
+ return hr;
+ };
+
+ hr = doFinishOpen(asyncResult);
+
+ m_CancelCookie = nullptr;
+ m_SourceResolver = nullptr;
+
+ if (SUCCEEDED(hr))
+ hr = ApplyTopology();
+
+ if (FAILED(hr))
+ CloseSession();
+
+ return hr;
+ }
+
+ MediaEventType eventType = MEUnknown;
+ com_ptr<IMFMediaEvent> mediaEvent;
+ SAFE_CALL(m_MediaSession->EndGetEvent(asyncResult, &mediaEvent));
+ SAFE_CALL(mediaEvent->GetType(&eventType));
+
+ if (eventType == MESessionClosed)
+ // The session was closed.
+ // The application is waiting on the m_CloseEvent mediaEvent handle.
+ SetEvent(m_CloseEvent);
+ else
+ // For all other events, get the next mediaEvent in the queue.
+ SAFE_CALL(m_MediaSession->BeginGetEvent(this, nullptr));
+
+ // Check the application state.
+
+ // If a call to IMFMediaSession::Close is pending, it means the
+ // application is waiting on the m_CloseEvent mediaEvent and
+ // the application's message loop is blocked.
+
+ // Otherwise, post a private window message to the application.
+ if (m_State != Closing)
+ {
+ // Leave a reference count on the mediaEvent.
+ m_EventCallback(this, std::move(mediaEvent), eventType);
+ }
+
+ return S_OK;
+}
+
+// Create a media source from a URL.
+//static HRESULT CreateMediaSource(const wchar_t* url, IMFMediaSource** mediaSource)
+//{
+// HRESULT hr = S_OK;
+//
+// MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
+//
+// com_ptr<IMFSourceResolver> sourceResolver;
+// com_ptr<IUnknown> source;
+//
+// // Create the source resolver.
+// SAFE_CALL(MFCreateSourceResolver(&sourceResolver));
+//
+// // Use the source resolver to create the media source.
+//
+// // Note: For simplicity this sample uses the synchronous method to create
+// // the media source. However, creating a media source can take a noticeable
+// // amount of time, especially for a network source. For a more responsive
+// // UI, use the asynchronous BeginCreateObjectFromURL method.
+// SAFE_CALL(sourceResolver->CreateObjectFromURL(
+// url, // URL of the source.
+// MF_RESOLUTION_MEDIASOURCE, // Create a source object.
+// NULL, // Optional property store.
+// &objectType, // Receives the created object type.
+// &source)); // Receives a pointer to the media source.
+//
+// // Get the IMFMediaSource interface from the media source.
+// SAFE_CALL(source->QueryInterface(IID_PPV_ARGS(mediaSource)));
+//
+// return hr;
+//}
+
+static HRESULT CreateMediaSinkActivate(IMFStreamDescriptor* streamDescriptor, IMFActivate** outActivate)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFMediaTypeHandler> mediaTypeHandler;
+ com_ptr<IMFActivate> activate;
+
+ // Get the media type handler for the stream.
+ SAFE_CALL(streamDescriptor->GetMediaTypeHandler(&mediaTypeHandler));
+
+ // Get the major media type.
+ GUID majorType = GUID_NULL;
+ SAFE_CALL(mediaTypeHandler->GetMajorType(&majorType));
+
+ // Create an IMFActivate object for the renderer, based on the media type.
+ if (MFMediaType_Audio == majorType)
+ {
+ // Create the audio renderer.
+ SAFE_CALL(g_MF.MFCreateAudioRendererActivate(&activate));
+ }
+ else // Unknown stream type.
+ {
+ SAFE_CALL(E_FAIL);
+ // Optionally, you could deselect this stream instead of failing.
+ }
+
+ // Return IMFActivate pointer to caller.
+ *outActivate = activate.detach();
+
+ return hr;
+}
+
+// Add a source node to a topology.
+static HRESULT AddSourceNode(IMFTopology* topology, IMFMediaSource* mediaSource, IMFPresentationDescriptor* presentationDescriptor, IMFStreamDescriptor* streamDescriptor, IMFTopologyNode* *outTopologyNode)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFTopologyNode> node;
+
+ // Create the node.
+ SAFE_CALL(g_MF.MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node));
+ SAFE_CALL(node->SetUnknown(MF_TOPONODE_SOURCE, mediaSource));
+ SAFE_CALL(node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDescriptor));
+ SAFE_CALL(node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDescriptor));
+ SAFE_CALL(topology->AddNode(node));
+
+ *outTopologyNode = node.detach();
+
+ return hr;
+}
+
+// Add an output node to a topology.
+static HRESULT AddOutputNode(IMFTopology* topology, IMFActivate* activate, DWORD streamSinkId, IMFTopologyNode** outTopologyNode)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFTopologyNode> node;
+
+ SAFE_CALL(g_MF.MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node));
+ SAFE_CALL(node->SetObject(activate));
+ SAFE_CALL(node->SetUINT32(MF_TOPONODE_STREAMID, streamSinkId));
+ SAFE_CALL(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, false));
+ SAFE_CALL(topology->AddNode(node));
+
+ *outTopologyNode = node.detach();
+
+ return hr;
+}
+
+// Add a topology branch for one stream.
+//
+// For each stream, this function does the following:
+//
+// 1. Creates a source node associated with the stream.
+// 2. Creates an output node for the renderer.
+// 3. Connects the two nodes.
+//
+// The media session will add any decoders that are needed.
+static HRESULT AddBranchToPartialTopology(IMFTopology* topology, IMFMediaSource* mediaSource, IMFPresentationDescriptor* presentationDescriptor, DWORD streamIndex)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFStreamDescriptor> streamDescriptor;
+ com_ptr<IMFActivate> activate;
+ com_ptr<IMFTopologyNode> sourceNode;
+ com_ptr<IMFTopologyNode> outputNode;
+
+ BOOL isSelected = false;
+ SAFE_CALL(presentationDescriptor->GetStreamDescriptorByIndex(streamIndex, &isSelected, &streamDescriptor));
+ if (!isSelected)
+ return hr;
+
+ // Create the media sink activation object.
+ SAFE_CALL(CreateMediaSinkActivate(streamDescriptor, &activate));
+ SAFE_CALL(AddSourceNode(topology, mediaSource, presentationDescriptor, streamDescriptor, &sourceNode));
+ SAFE_CALL(AddOutputNode(topology, activate, 0, &outputNode));
+ SAFE_CALL(sourceNode->ConnectOutput(0, outputNode, 0));
+
+ return hr;
+}
+
+// Create a playback topology from a media source.
+static HRESULT CreatePlaybackTopology(IMFMediaSource* mediaSourcee, IMFPresentationDescriptor* presentationDescriptor, IMFTopology** outTopology)
+{
+ HRESULT hr = S_OK;
+
+ com_ptr<IMFTopology> topology;
+
+ // Create a new topology.
+ SAFE_CALL(g_MF.MFCreateTopology(&topology));
+
+ // Get the number of streams in the media source.
+ DWORD streamDescriptorCount = 0;
+ SAFE_CALL(presentationDescriptor->GetStreamDescriptorCount(&streamDescriptorCount));
+
+ // For each stream, create the topology nodes and add them to the topology.
+ for (DWORD i = 0; i < streamDescriptorCount; ++i)
+ SAFE_CALL(AddBranchToPartialTopology(topology, mediaSourcee, presentationDescriptor, i));
+
+ *outTopology = topology.detach();
+
+ return hr;
+}
+
+# undef SAFE_CALL
+
+struct _player_t
+{
+ com_ptr<MediaPlayer> player;
+};
+
+extern "C" player2_t WMFPlayerCreate()
+{
+ if (!MFLoad())
+ return nullptr;
+
+ com_ptr<MediaPlayer> player;
+ auto hr = MediaPlayer::Create(nullptr, &player);
+ if (FAILED(hr))
+ return nullptr;
+
+ auto out = new _player_t();
+ out->player = player;
+ return out;
+}
+
+extern "C" void WMFPlayerDestroy(player2_t player)
+{
+ if (player)
+ delete player;
+}
+
+extern "C" void WMFPlayerSetVolume(player2_t player, float volume)
+{
+ const float attenuation = powf(10.0f, volume / 20.0f);
+ const float linearVolume = max(0.0f, min(1.0f, attenuation));
+ player->player->SetMasterVolume(linearVolume);
+}
+
+extern "C" float WMFPlayerGetVolume(player2_t player)
+{
+ const float linearVolume = player->player->GetMasterVolume();
+ const float volume = linearVolume > 0.0f ? 20.0f * log10f(linearVolume) : 0.0f;
+ return volume;
+}
+
+extern "C" void WMFPlayerSetGain(player2_t player, float gainDb)
+{
+ player->player->SetReplayGain(gainDb);
+}
+
+extern "C" float WMFPlayerGetGain(player2_t player)
+{
+ return player->player->GetReplayGain();
+}
+
+extern "C" double WMFPlayerGetDuration(player2_t player)
+{
+ if (auto duration = player->player->GetDuration())
+ return *duration;
+ else
+ return 0.0f;
+}
+
+extern "C" double WMFPlayerGetTime(player2_t player)
+{
+ if (auto duration = player->player->GetPresentationTime())
+ return *duration;
+ else
+ return 0.0f;
+}
+
+extern "C" bool WMFPlayerOpen(player2_t player, const char* url)
+{
+ auto urlLength = strlen(url);
+ if (urlLength == 0)
+ return false;
+
+ int bufferLength = ::MultiByteToWideChar(CP_UTF8, 0, url, urlLength, nullptr, 0);
+ if (bufferLength == 0)
+ return false;
+
+ wchar_t* buffer = new wchar_t[bufferLength + 1];
+ int result = ::MultiByteToWideChar(CP_UTF8, 0, url, urlLength, buffer, bufferLength);
+ if (result == 0)
+ {
+ delete [] buffer;
+ return false;
+ }
+
+ buffer[bufferLength] = 0;
+
+ auto hr = player->player->OpenURL(buffer);
+
+ delete[] buffer;
+
+ while (player->player->GetState() == MediaPlayer::OpenPending)
+ Sleep(1);
+
+ return SUCCEEDED(hr);
+}
+
+extern "C" bool WMFPlayerPlay(player2_t player)
+{
+ return SUCCEEDED(player->player->Play());
+}
+
+extern "C" bool WMFPlayerPause(player2_t player)
+{
+ return SUCCEEDED(player->player->Pause());
+}
+
+extern "C" bool WMFPlayerStop(player2_t player)
+{
+ return SUCCEEDED(player->player->Stop());
+}
+
+extern "C" bool WMFPlayerFinish(player2_t player)
+{
+ return SUCCEEDED(player->player->Stop());
+}
+
+extern "C" bool WMFPlayerIsPlaying(player2_t player)
+{
+ auto state = player->player->GetState();
+ return state == MediaPlayer::Started;
+}
+
+extern "C" bool WMFPlayerIsPaused(player2_t player)
+{
+ auto state = player->player->GetState();
+ return state == MediaPlayer::Paused;
+}
+
+extern "C" bool WMFPlayerIsStopped(player2_t player)
+{
+ auto state = player->player->GetState();
+ return state == MediaPlayer::Stopped || state == MediaPlayer::Closing || state == MediaPlayer::Closed;
+}
+
+extern "C" bool WMFPlayerIsFinished(player2_t player)
+{
+ return WMFPlayerIsStopped(player);
+ //auto state = player->player->GetState();
+ //return state == MediaPlayer::Closing || state == MediaPlayer::Closed;
+}
+
+extern "C" player2_iface player2_windows_media_foundation =
+{
+ /*.Id =*/ "mf",
+ /*.Name =*/ "Windows Media Foundation",
+ /*.Create =*/ WMFPlayerCreate,
+ /*.Destroy =*/ WMFPlayerDestroy,
+ /*.SetVolume =*/ WMFPlayerSetVolume,
+ /*.GetVolume =*/ WMFPlayerGetVolume,
+ /*.SetGain =*/ WMFPlayerSetGain,
+ /*.GetGain =*/ WMFPlayerGetGain,
+ /*.GetDuration =*/ WMFPlayerGetDuration,
+ /*.GetTime =*/ WMFPlayerGetTime,
+ /*.Open =*/ WMFPlayerOpen,
+ /*.Play =*/ WMFPlayerPlay,
+ /*.Pause =*/ WMFPlayerPause,
+ /*.Stop =*/ WMFPlayerStop,
+ /*.Finish =*/ WMFPlayerFinish,
+ /*.IsPlaying =*/ WMFPlayerIsPlaying,
+ /*.IsPaused =*/ WMFPlayerIsPaused,
+ /*.IsStopped =*/ WMFPlayerIsStopped,
+ /*.IsFinished =*/ WMFPlayerIsFinished
+};
diff --git a/src/player/backends/media_foundation.h b/src/player/backends/media_foundation.h
new file mode 100644
index 0000000..cf10122
--- /dev/null
+++ b/src/player/backends/media_foundation.h
@@ -0,0 +1,115 @@
+# ifndef __TD__BASIC_MEDIA_PLAYER_H__
+# define __TD__BASIC_MEDIA_PLAYER_H__
+# pragma once
+
+# include <mfapi.h>
+# include <mfidl.h>
+# include "utility/com_ptr.h"
+# include "utility/optional.h"
+
+class MediaPlayer;
+
+typedef void (*MediaPlayerEventCallback)(MediaPlayer* player, com_ptr<IMFMediaEvent>, MediaEventType);
+
+class MediaPlayer:
+ private IMFAsyncCallback
+{
+public:
+ enum State
+ {
+ Closed = 0, // no session
+ Ready, // session was created, ready to open file
+ OpenPending, // session is opening a file
+ Started, // session is playing a file
+ Paused, // session is paused
+ Stopped, // session is stopped (but ready to play)
+ Closing // session was closed, waiting for async callback
+ };
+
+ static HRESULT Create(MediaPlayerEventCallback eventCallback, MediaPlayer** outMediaPlayer);
+
+ // IUnknown
+ virtual ULONG STDMETHODCALLTYPE AddRef() override;
+ virtual ULONG STDMETHODCALLTYPE Release() override;
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** object) override;
+
+ HRESULT OpenURL(const wchar_t* url);
+ HRESULT Play();
+ HRESULT Pause();
+ HRESULT Stop();
+ HRESULT Shutdown();
+
+ State GetState() const;
+
+ void SetMasterVolume(float volume);
+ float GetMasterVolume() const;
+
+ void SetReplayGain(float replayGain);
+ float GetReplayGain() const;
+
+ optional<float> GetPresentationTime() const;
+ optional<float> GetDuration() const;
+
+ void SetUserData(void* userData);
+ void* GetUserData() const;
+
+ HRESULT HandleEvent(IMFMediaEvent* mediaEvent);
+
+protected:
+ MediaPlayer(MediaPlayerEventCallback eventCallback);
+ virtual ~MediaPlayer();
+
+ HRESULT Initialize();
+ HRESULT CreateSession();
+ HRESULT CloseSession();
+ HRESULT StartPlayback();
+ HRESULT ApplyTopology();
+
+ HRESULT ApplyMasterVolume(float volume);
+ HRESULT ApplyReplayGain(float replayGain);
+
+ const com_ptr<IMFMediaSession>& GetMediaSession() const { return m_MediaSession; }
+ const com_ptr<IMFMediaSource>& GetMediaSource() const { return m_MediaSource; }
+
+ virtual HRESULT OnTopologyStatus(IMFMediaEvent* mediaEvent);
+ virtual HRESULT OnPresentationEnded(IMFMediaEvent* mediaEvent);
+ virtual HRESULT OnNewPresentation(IMFMediaEvent* mediaEvent);
+
+ // Override to handle additional session events.
+ virtual HRESULT OnSessionEvent(IMFMediaEvent* mediaEvent, MediaEventType mediaEventType);
+
+ virtual HRESULT OnStateChange(State state, State previousState);
+
+private:
+ // IMFAsyncCallback
+ virtual HRESULT STDMETHODCALLTYPE GetParameters(DWORD* flags, DWORD* queue);
+ virtual HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult* asyncResult);
+
+ HRESULT SetState(State state);
+
+ ULONG m_RefCount;
+
+ com_ptr<IMFSourceResolver> m_SourceResolver;
+ com_ptr<IUnknown> m_CancelCookie;
+
+ com_ptr<IMFMediaSession> m_MediaSession;
+ com_ptr<IMFMediaSource> m_MediaSource;
+
+ com_ptr<IMFSimpleAudioVolume> m_SimpleAudioVolume;
+ com_ptr<IMFPresentationClock> m_PresentationClock;
+ com_ptr<IMFAudioStreamVolume> m_StreamAudioVolume;
+
+ optional<float> m_SetMasterVolume;
+ mutable float m_MasterVolume;
+ mutable float m_ReplayGain;
+
+ MediaPlayerEventCallback m_EventCallback;
+
+ void* m_UserData;
+
+ State m_State;
+ HANDLE m_CloseEvent;
+};
+
+
+# endif // __TD__BASIC_MEDIA_PLAYER_H__ \ No newline at end of file
diff --git a/src/player/backends/utility/com_ptr.h b/src/player/backends/utility/com_ptr.h
new file mode 100644
index 0000000..23534b2
--- /dev/null
+++ b/src/player/backends/utility/com_ptr.h
@@ -0,0 +1,148 @@
+# ifndef __TD__COM_PTR_H__
+# define __TD__COM_PTR_H__
+# pragma once
+
+template <typename T>
+class com_ptr
+{
+//private:
+// typedef void (com_ptr::*bool_type)() const;
+// void safe_bool() const {}
+//
+//
+public:
+ com_ptr():
+ _ptr(nullptr)
+ {
+ }
+
+ com_ptr(T* ptr, bool add_ref = false):
+ _ptr(ptr)
+ {
+ if (_ptr && add_ref)
+ _ptr->AddRef();
+ }
+
+ com_ptr(const com_ptr& rhs):
+ _ptr(rhs._ptr)
+ {
+ if (_ptr)
+ _ptr->AddRef();
+ }
+
+ com_ptr(com_ptr&& rhs):
+ _ptr(rhs._ptr)
+ {
+ rhs._ptr = nullptr;
+ }
+
+ template <typename U>
+ com_ptr(const com_ptr<U>& rhs):
+ _ptr(rhs._ptr)
+ {
+ if (_ptr)
+ _ptr->AddRef();
+ }
+
+ ~com_ptr()
+ {
+ if (_ptr)
+ _ptr->Release();
+ }
+
+ com_ptr& operator = (const com_ptr& rhs)
+ {
+ com_ptr(rhs).swap(*this);
+ return *this;
+ }
+
+ com_ptr& operator = (com_ptr&& rhs)
+ {
+ com_ptr(static_cast<com_ptr&&>(rhs)).swap(*this);
+ return *this;
+ }
+
+ template <typename U>
+ com_ptr& operator = (const com_ptr<U>& rhs)
+ {
+ com_ptr(rhs).swap(*this):
+ return *this;
+ }
+
+ com_ptr& operator = (T* rhs)
+ {
+ com_ptr(rhs).swap(*this);
+ return *this;
+ }
+
+ //operator bool_type() const
+ //{
+ // return _ptr ? &com_ptr::safe_bool : nullptr;
+ //}
+
+ //bool_type operator !() const
+ //{
+ // return !((bool_type)*this);
+ //}
+
+ void reset()
+ {
+ com_ptr().swap(*this);
+ }
+
+ void reset(T* rhs)
+ {
+ com_ptr(rhs).swap(*this);
+ }
+
+ void reset(T* rhs, bool add_ref)
+ {
+ com_ptr(rhs, add_ref).swap(*this);
+ }
+
+ T* get() const
+ {
+ return _ptr;
+ }
+
+ T* detach()
+ {
+ auto result = _ptr;
+ _ptr = nullptr;
+ return result;
+ }
+
+ void swap(com_ptr& rhs)
+ {
+ T* temp = rhs._ptr;
+ rhs._ptr = _ptr;
+ _ptr = temp;
+ }
+
+ T& operator * () const { return *_ptr; }
+ T* operator -> () const { return _ptr; }
+
+ operator T* () const { return _ptr; }
+ T** operator & () { return &_ptr; }
+
+private:
+ T* _ptr;
+};
+
+template <typename T, typename U> inline bool operator==(const com_ptr<T>& a, const com_ptr<U>& b) { return a.get() == b.get(); }
+template <typename T, typename U> inline bool operator!=(const com_ptr<T>& a, const com_ptr<U>& b) { return a.get() != b.get(); }
+template <typename T, typename U> inline bool operator==(const com_ptr<T>& a, U* b) { return a.get() == b; }
+template <typename T, typename U> inline bool operator!=(const com_ptr<T>& a, U* b) { return a.get() != b; }
+template <typename T, typename U> inline bool operator==(T* a, const com_ptr<U>& b) { return a == b.get(); }
+template <typename T, typename U> inline bool operator!=(T* a, const com_ptr<U>& b) { return a != b.get(); }
+template <typename T> inline bool operator==(const com_ptr<T>& p, std::nullptr_t) { return p.get() == nullptr; }
+template <typename T> inline bool operator==(std::nullptr_t, const com_ptr<T>& p) { return p.get() == nullptr; }
+template <typename T> inline bool operator!=(const com_ptr<T>& p, std::nullptr_t) { return p.get() != nullptr; }
+template <typename T> inline bool operator!=(std::nullptr_t, const com_ptr<T>& p) { return p.get() != nullptr; }
+template <typename T> inline bool operator<(const com_ptr<T>& a, const com_ptr<T>& b) { return std::less<T*>()(a.get(), b.get()); }
+template <typename T> inline bool operator<=(const com_ptr<T>& a, const com_ptr<T>& b) { return std::less_equal<T*>()(a.get(), b.get()); }
+template <typename T> inline bool operator>(const com_ptr<T>& a, const com_ptr<T>& b) { return std::greater<T*>()(a.get(), b.get()); }
+template <typename T> inline bool operator>=(const com_ptr<T>& a, const com_ptr<T>& b) { return std::greater_equal<T*>()(a.get(), b.get()); }
+template <typename T> void swap(com_ptr<T> & lhs, com_ptr<T> & rhs) { lhs.swap(rhs); }
+
+# endif // __TD__COM_PTR_H__ \ No newline at end of file
diff --git a/src/player/backends/utility/optional.h b/src/player/backends/utility/optional.h
new file mode 100644
index 0000000..9ee0b71
--- /dev/null
+++ b/src/player/backends/utility/optional.h
@@ -0,0 +1,517 @@
+//------------------------------------------------------------------------------
+//
+//------------------------------------------------------------------------------
+# ifndef __TD__OPTIONAL_H__
+# define __TD__OPTIONAL_H__
+# pragma once
+
+# include <type_traits>
+# include <utility>
+# include <cassert>
+
+
+//------------------------------------------------------------------------------
+struct nullopt_t
+{
+ struct init {};
+ nullopt_t(init) {}
+};
+
+const nullopt_t nullopt((nullopt_t::init()));
+
+template <typename T>
+struct optional
+{
+private:
+ typedef typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type storage_type;
+
+ typedef void (optional::*bool_type)() const;
+ void safe_bool() const { }
+
+
+public:
+ optional();
+ optional(nullopt_t);
+ optional(const optional& v);
+ optional(optional&& v);
+ optional(const T& v);
+ optional(T&& v);
+ ~optional();
+
+ operator bool_type() const;
+
+ optional& operator=(nullopt_t);
+
+ optional& operator=(const optional& other);
+ optional& operator=(optional&& other);
+
+ // The function does not participate in overload resolution unless std::is_same<std::decay_t<U>, T>::value is true
+ template <typename U>
+ // optional& operator=(U&& value);
+ optional& operator=(typename std::enable_if<std::is_same<typename std::decay<U>::type, T>::value, U>::type&& value);
+
+ T* operator->();
+ const T* operator->() const;
+ T& operator*();
+ const T& operator*() const;
+
+ T& value();
+ const T& value() const;
+
+# if defined(_MSC_VER) && (_MSC_VER > 1600)
+ template <typename U>
+ T value_or(U&& default_value) const &;
+
+ template <typename U>
+ T value_or(U&& default_value) &&;
+# else
+ T value_or(const T& default_value);
+ T value_or(const T& default_value) const;
+# endif
+
+ bool has_value() const;
+
+ void swap(optional& other);
+
+private:
+ T* value_ptr() { return reinterpret_cast< T*>(&_value); }
+ const T* value_ptr() const { return reinterpret_cast<const T*>(&_value); }
+
+ T& value_ref() { return *value_ptr(); }
+ const T& value_ref() const { return *value_ptr(); }
+
+ bool _has_value;
+ storage_type _value;
+# if defined(_DEBUG)
+ const T& _preview;
+# endif
+};
+
+
+//------------------------------------------------------------------------------
+template <typename T>
+inline optional<T>::optional():
+ _has_value(false)
+# if defined(_DEBUG)
+ ,_preview(value_ref())
+# endif
+{
+}
+
+template <typename T>
+inline optional<T>::optional(nullopt_t):
+ _has_value(false)
+# if defined(_DEBUG)
+ , _preview(value_ref())
+# endif
+{
+}
+
+template <typename T>
+inline optional<T>::optional(const optional& v):
+ _has_value(v._has_value)
+# if defined(_DEBUG)
+ , _preview(value_ref())
+# endif
+{
+ if (_has_value)
+ new (value_ptr()) T(v.value_ref());
+}
+
+template <typename T>
+inline optional<T>::optional(optional&& v):
+ _has_value(v._has_value)
+# if defined(_DEBUG)
+ , _preview(value_ref())
+# endif
+{
+ if (!_has_value)
+ return;
+
+ new (value_ptr()) T(std::move(v.value_ref()));
+ v.value_ref().~T();
+
+ v._has_value = false;
+}
+
+template <typename T>
+inline optional<T>::optional(const T& v):
+ _has_value(true)
+# if defined(_DEBUG)
+ , _preview(value_ref())
+# endif
+{
+ new (value_ptr()) T(v);
+}
+
+template <typename T>
+inline optional<T>::optional(T&& v):
+ _has_value(true)
+# if defined(_DEBUG)
+ , _preview(value_ref())
+# endif
+{
+ new (value_ptr()) T(std::forward<T>(v));
+}
+
+template <typename T>
+inline optional<T>::~optional()
+{
+ if (_has_value)
+ value_ref().~T();
+}
+
+template <typename T>
+inline optional<T>::operator bool_type() const
+{
+ return _has_value ? &optional::safe_bool : nullptr;
+}
+
+template <typename T>
+inline optional<T>& optional<T>::operator= (nullopt_t)
+{
+ optional().swap(*this);
+ _has_value = false;
+ return *this;
+}
+
+template <typename T>
+inline optional<T>& optional<T>::operator=(const optional& other)
+{
+ optional(other).swap(*this);
+ return *this;
+}
+
+template <typename T>
+inline optional<T>& optional<T>::operator=(optional&& other)
+{
+ optional(std::forward<optional>(other)).swap(*this);
+ return *this;
+}
+
+template <typename T>
+template <typename U>
+inline optional<T>& optional<T>::operator=(typename std::enable_if<std::is_same<typename std::decay<U>::type, T>::value, U>::type&& value)
+{
+ optional<T>(value).swap(*this);
+ return *this;
+}
+
+
+template <typename T>
+inline T* optional<T>::operator->()
+{
+ assert(_has_value);
+ return value_ptr();
+}
+
+template <typename T>
+inline const T* optional<T>::operator->() const
+{
+ assert(_has_value);
+ return value_ptr();
+}
+
+template <typename T>
+inline T& optional<T>::operator*()
+{
+ assert(_has_value);
+ return value_ref();
+}
+
+template <typename T>
+inline const T& optional<T>::operator*() const
+{
+ assert(_has_value);
+ return value_ref();
+}
+
+template <typename T>
+inline T& optional<T>::value()
+{
+ assert(_has_value);
+ return value_ref();
+}
+
+template <typename T>
+inline const T& optional<T>::value() const
+{
+ assert(_has_value);
+ return value_ref();
+}
+
+# if defined(_MSC_VER) && (_MSC_VER > 1600)
+template <typename T>
+template <typename U>
+inline T optional<T>::value_or(U&& default_value) const &
+{
+ return bool(*this) ? value_ref() : static_cast<T>(std::forward<U>(default_value));
+}
+
+template <typename T>
+template <typename U>
+inline T optional<T>::value_or(U&& default_value) &&
+{
+ return bool(*this) ? std::move(value_ref()) : static_cast<T>(std::forward<U>(default_value));
+}
+# else
+template <typename T>
+T optional<T>::value_or(const T& default_value)
+{
+ return _has_value ? value_ref() : default_value;
+}
+
+template <typename T>
+T optional<T>::value_or(const T& default_value) const
+{
+ return _has_value ? value_ref() : default_value;
+}
+# endif
+
+template <typename T>
+inline bool optional<T>::has_value() const
+{
+ return _has_value;
+}
+
+template <typename T>
+inline void optional<T>::swap(optional& other)
+{
+ using std::swap;
+
+ if (_has_value && other._has_value)
+ {
+ swap(value_ref(), other.value_ref());
+ }
+ else if (_has_value && !other._has_value)
+ {
+ new (other.value_ptr()) T(std::forward<T>(value_ref()));
+
+ value_ref().~T();
+
+ _has_value = false;
+ other._has_value = true;
+ }
+ else if (!_has_value && other._has_value)
+ {
+ new (value_ptr()) T(std::forward<T>(other.value_ref()));
+
+ other.value_ref().~T();
+
+ _has_value = true;
+ other._has_value = false;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+template <typename T>
+optional<T> make_optional(T&& v)
+{
+ return optional<T>(std::forward<T>(v));
+}
+
+template <typename T>
+inline void swap(optional<T>& lhs, optional<T>& rhs)
+{
+ lhs.swap(rhs);
+}
+
+template <typename T>
+inline bool operator==(const optional<T>& lhs, const optional<T>& rhs)
+{
+ if (static_cast<bool>(lhs) != static_cast<bool>(rhs))
+ return false;
+ if (!static_cast<bool>(lhs))
+ return true;
+ return *lhs == *rhs;
+}
+
+template <typename T>
+bool operator!=(const optional<T>& lhs, const optional<T>& rhs)
+{
+ return !(lhs == rhs);
+}
+
+template <typename T>
+inline bool operator<(const optional<T>& lhs, const optional<T>& rhs)
+{
+ if (!static_cast<bool>(rhs))
+ return false;
+ if (!static_cast<bool>(lhs))
+ return true;
+ return *lhs < *rhs;
+}
+
+template <typename T>
+inline bool operator>(const optional<T>& lhs, const optional<T>& rhs)
+{
+ return rhs < lhs;
+}
+
+template <typename T>
+inline bool operator<=(const optional<T>& lhs, const optional<T>& rhs)
+{
+ return !(rhs < lhs);
+}
+
+template <typename T>
+inline bool operator>=(const optional<T>& lhs, const optional<T>& rhs)
+{
+ return !(lhs < rhs);
+}
+
+template <typename T>
+inline bool operator==(const optional<T>& opt, nullopt_t)
+{
+ return !static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator==(nullopt_t, const optional<T>& opt)
+{
+ return static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator!=(const optional<T>& opt, nullopt_t)
+{
+ return static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator!=(nullopt_t, const optional<T>& opt)
+{
+ return !static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator<(const optional<T>& opt, nullopt_t)
+{
+ return false;
+}
+
+template <typename T>
+inline bool operator<(nullopt_t, const optional<T>& opt)
+{
+ return static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator<=(const optional<T>& opt, nullopt_t)
+{
+ return !opt;
+}
+
+template <typename T>
+inline bool operator<=(nullopt_t, const optional<T>& opt)
+{
+ return true;
+}
+
+template <typename T>
+inline bool operator>(const optional<T>& opt, nullopt_t)
+{
+ return static_cast<bool>(opt);
+}
+
+template <typename T>
+inline bool operator>(nullopt_t, const optional<T>& opt)
+{
+ return false;
+}
+
+template <typename T>
+inline bool operator>=(const optional<T>&, nullopt_t)
+{
+ return true;
+}
+
+template <typename T>
+inline bool operator>=(nullopt_t, const optional<T>& opt)
+{
+ return !opt;
+}
+
+template <typename T>
+inline bool operator==(const optional<T>& opt, const T& v)
+{
+ return static_cast<bool>(opt) ? *opt == v : false;
+}
+
+template <typename T>
+inline bool operator==(const T& v, const optional<T>& opt)
+{
+ return static_cast<bool>(opt) ? *opt == v : false;
+}
+
+template <typename T>
+inline bool operator!=(const optional<T>& opt, const T& v)
+{
+ return static_cast<bool>(opt) ? *opt != v : true;
+}
+
+template <typename T>
+inline bool operator!=(const T& v, const optional<T>& opt)
+{
+ return static_cast<bool>(opt) ? *opt != v : true;
+}
+
+template <typename T>
+inline bool operator<(const optional<T>& opt, const T& v)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? less<T>(*opt, v) : true;
+}
+
+template <typename T>
+inline bool operator<(const T& v, const optional<T>& opt)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? less<T>(v, *opt) : false;
+}
+
+template <typename T>
+inline bool operator<=(const optional<T>& opt, const T& v)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? less_equal<T>(*opt, v) : true;
+}
+
+template <typename T>
+inline bool operator<=(const T& v, const optional<T>& opt)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? less_equal<T>(v, *opt) : false;
+}
+
+template <typename T>
+inline bool operator>(const optional<T>& opt, const T& v)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? greater<T>(*opt, v) : false;
+}
+
+template <typename T>
+inline bool operator>(const T& v, const optional<T>& opt)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? greater<T>(v, *opt) : true;
+}
+
+template <typename T>
+inline bool operator>=(const optional<T>& opt, const T& v)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? greater_equal<T>(*opt, v) : false;
+}
+
+template <typename T>
+inline bool operator>=(const T& v, const optional<T>& opt)
+{
+ using namespace std;
+ return static_cast<bool>(opt) ? greater_equal<T>(v, *opt) : true;
+}
+
+
+# endif // __TD__OPTIONAL_H__
diff --git a/src/player/player2.c b/src/player/player2.c
new file mode 100644
index 0000000..cd9beae
--- /dev/null
+++ b/src/player/player2.c
@@ -0,0 +1,211 @@
+/*
+Copyright (c) 2015
+ Micha³ Cichoñ <thedmd@interia.pl>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/* receive/play audio stream */
+
+/* based on DShow example player */
+
+#include "config.h"
+#include "player2_private.h"
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+
+# define length_of(x) (sizeof(x)/sizeof(*(x)))
+
+static player2_iface* player2_backends[] =
+{
+ &player2_windows_media_foundation, // expermiental
+ &player2_direct_show,
+};
+
+struct _player_t
+{
+ player2_iface* backend;
+ player2_t player;
+};
+
+bool BarPlayer2Init(player2_t* outPlayer, const char* defaultPlayer)
+{
+ player2_t player;
+ struct _player_t result;
+ int i;
+
+ memset(&result, 0, sizeof(struct _player_t));
+
+ for (i = 0; i < length_of(player2_backends); ++i)
+ {
+ player2_iface* backend = player2_backends[i];
+
+ bool acceptPlayer = true;
+ if (defaultPlayer && !(strcmp(backend->Id, defaultPlayer) == 0))
+ acceptPlayer = false;
+
+ if (acceptPlayer)
+ result.player = backend->Create();
+
+ if (result.player)
+ {
+ result.backend = backend;
+ break;
+ }
+ }
+
+ if (!result.backend)
+ return false;
+
+ player = malloc(sizeof(struct _player_t));
+ if (!player)
+ return false;
+
+ *player = result;
+
+ *outPlayer = player;
+
+ return true;
+}
+
+void BarPlayer2Destroy(player2_t player)
+{
+ if (player->player)
+ {
+ player->backend->Destroy(player->player);
+ player->player = NULL;
+ }
+}
+
+void BarPlayer2SetVolume(player2_t player, float volume)
+{
+ if (player->player)
+ player->backend->SetVolume(player->player, volume);
+}
+
+float BarPlayer2GetVolume(player2_t player)
+{
+ if (player->player)
+ return player->backend->GetVolume(player->player);
+ else
+ return 0.0f;
+}
+
+void BarPlayer2SetGain(player2_t player, float gainDb)
+{
+ if (player->player)
+ player->backend->SetGain(player->player, gainDb);
+}
+
+float BarPlayer2GetGain(player2_t player)
+{
+ if (player->player)
+ return player->backend->GetGain(player->player);
+ else
+ return 0.0f;
+}
+
+double BarPlayer2GetDuration(player2_t player)
+{
+ if (player->player)
+ return player->backend->GetDuration(player->player);
+ else
+ return 0.0f;
+}
+
+double BarPlayer2GetTime(player2_t player)
+{
+ if (player->player)
+ return player->backend->GetTime(player->player);
+ else
+ return 0.0f;
+}
+
+bool BarPlayer2Open(player2_t player, const char* url)
+{
+ if (player->player)
+ return player->backend->Open(player->player, url);
+ else
+ return false;
+}
+
+bool BarPlayer2Play(player2_t player)
+{
+ if (player->player)
+ return player->backend->Play(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2Pause(player2_t player)
+{
+ if (player->player)
+ return player->backend->Pause(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2Stop(player2_t player)
+{
+ if (player->player)
+ return player->backend->Stop(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2Finish(player2_t player)
+{
+ if (player->player)
+ return player->backend->Finish(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2IsPlaying(player2_t player)
+{
+ if (player->player)
+ return player->backend->IsPlaying(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2IsPaused(player2_t player)
+{
+ if (player->player)
+ return player->backend->IsPaused(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2IsStopped(player2_t player)
+{
+ if (player->player)
+ return player->backend->IsStopped(player->player);
+ else
+ return false;
+}
+
+bool BarPlayer2IsFinished(player2_t player)
+{
+ if (player->player)
+ return player->backend->IsFinished(player->player);
+ else
+ return true;
+} \ No newline at end of file
diff --git a/src/player2.h b/src/player/player2.h
index b426ee2..710b35d 100644
--- a/src/player2.h
+++ b/src/player/player2.h
@@ -1,6 +1,6 @@
/*
Copyright (c) 2015
- Micha³ Cichoñ <thedmd@interia.pl>
+ Micha³ Cichoñ <thedmd@interia.pl>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,8 +21,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-#ifndef SRC_PLAYER2_H_CN979RE9
-#define SRC_PLAYER2_H_CN979RE9
+#ifndef __PIANOBAR_PLAYER2_H__
+#define __PIANOBAR_PLAYER2_H__
+#pragma once
#include "config.h"
@@ -30,23 +31,23 @@ THE SOFTWARE.
typedef struct _player_t *player2_t;
-bool BarPlayer2Init (player2_t*);
-void BarPlayer2Destroy (player2_t);
-void BarPlayer2SetVolume (player2_t,float);
-float BarPlayer2GetVolume (player2_t);
-void BarPlayer2SetGain (player2_t, float);
-float BarPlayer2GetGain (player2_t);
-double BarPlayer2GetDuration (player2_t);
-double BarPlayer2GetTime (player2_t);
-bool BarPlayer2Open (player2_t, const char*);
-bool BarPlayer2Play (player2_t);
-bool BarPlayer2Pause (player2_t);
-bool BarPlayer2Stop (player2_t);
-bool BarPlayer2Finish (player2_t);
-bool BarPlayer2IsPlaying (player2_t);
-bool BarPlayer2IsPaused (player2_t);
-bool BarPlayer2IsStopped (player2_t);
-bool BarPlayer2IsFinished (player2_t);
-
-#endif /* SRC_PLAYER2_H_CN979RE9 */
+bool BarPlayer2Init(player2_t* outPlayer, const char* defaultPlayer);
+void BarPlayer2Destroy(player2_t player);
+void BarPlayer2SetVolume(player2_t player, float volume);
+float BarPlayer2GetVolume(player2_t player);
+void BarPlayer2SetGain(player2_t player, float gainDb);
+float BarPlayer2GetGain(player2_t player);
+double BarPlayer2GetDuration(player2_t player);
+double BarPlayer2GetTime(player2_t player);
+bool BarPlayer2Open(player2_t player, const char* url);
+bool BarPlayer2Play(player2_t player);
+bool BarPlayer2Pause(player2_t player);
+bool BarPlayer2Stop(player2_t player);
+bool BarPlayer2Finish(player2_t player);
+bool BarPlayer2IsPlaying(player2_t player);
+bool BarPlayer2IsPaused(player2_t player);
+bool BarPlayer2IsStopped(player2_t player);
+bool BarPlayer2IsFinished(player2_t player);
+
+#endif /* __PIANOBAR_PLAYER2_H__ */
diff --git a/src/player/player2_private.h b/src/player/player2_private.h
new file mode 100644
index 0000000..757a07e
--- /dev/null
+++ b/src/player/player2_private.h
@@ -0,0 +1,35 @@
+#ifndef __PIANOBAR_PLAYER2_PRIVATE_H__
+#define __PIANOBAR_PLAYER2_PRIVATE_H__
+#pragma once
+
+#include "config.h"
+#include <stdbool.h>
+#include "player2.h"
+
+typedef struct _player2_iface
+{
+ const char* Id;
+ const char* Name;
+ player2_t (*Create) ();
+ void (*Destroy) (player2_t player);
+ void (*SetVolume) (player2_t player, float volume);
+ float (*GetVolume) (player2_t player);
+ void (*SetGain) (player2_t player, float gainDb);
+ float (*GetGain) (player2_t player);
+ double (*GetDuration) (player2_t player);
+ double (*GetTime) (player2_t player);
+ bool (*Open) (player2_t player, const char* url);
+ bool (*Play) (player2_t player);
+ bool (*Pause) (player2_t player);
+ bool (*Stop) (player2_t player);
+ bool (*Finish) (player2_t player);
+ bool (*IsPlaying) (player2_t player);
+ bool (*IsPaused) (player2_t player);
+ bool (*IsStopped) (player2_t player);
+ bool (*IsFinished) (player2_t player);
+} player2_iface;
+
+extern player2_iface player2_direct_show;
+extern player2_iface player2_windows_media_foundation;
+
+#endif /* __PIANOBAR_PLAYER2_PRIVATE_H__ */
diff --git a/src/player2.c b/src/player2.c
deleted file mode 100644
index 01fe33c..0000000
--- a/src/player2.c
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
-Copyright (c) 2015
- Micha³ Cichoñ <thedmd@interia.pl>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-
-/* receive/play audio stream */
-
-/* based on DShow example player */
-
-#include "config.h"
-#include "player2.h"
-#define COBJMACROS
-#define INITGUID
-#include <objbase.h>
-#include <dshow.h>
-#pragma comment(lib, "strmiids.lib")
-
-# define WM_GRAPH_EVENT (WM_APP + 1)
-
-enum { NO_GRAPH, RUNNING, PAUSED, STOPPED };
-
-static struct _player_static_t {
- bool done;
- bool initialized;
- bool hasCOM;
-} BarPlayerGlobal = { 0 };
-
-struct _player_t {
- int state;
- IGraphBuilder* graph;
- IMediaControl* control;
- IMediaEventEx* event;
- IBasicAudio* audio;
- IMediaSeeking* media;
- float volume; // dB
- float gain; // dB
-};
-
-static bool BarPlayer2StaticInit();
-static void BarPlayer2StaticTerm(void);
-
-static bool BarPlayer2StaticInit () {
- if (BarPlayerGlobal.done)
- return BarPlayerGlobal.initialized;
-
- BarPlayerGlobal.done = true;
-
- atexit(BarPlayer2StaticTerm);
-
- if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
- return false;
-
- BarPlayerGlobal.hasCOM = true;
-
- BarPlayerGlobal.initialized = true;
-
- return true;
-}
-
-static void BarPlayer2StaticTerm(void) {
- if (!BarPlayerGlobal.done)
- return;
-
- if (BarPlayerGlobal.hasCOM) {
- CoUninitialize();
- BarPlayerGlobal.hasCOM = false;
- }
-
- BarPlayerGlobal.initialized = false;
- BarPlayerGlobal.done = false;
-}
-
-static void BarPlayer2ApplyVolume(player2_t player) {
- long v = (long)((player->volume + player->gain) * 100.0f);
-
- if (!player->audio)
- return;
-
- if (v < -10000)
- v = -10000;
- if (v > 0)
- v = 0;
-
- IBasicAudio_put_Volume(player->audio, v);
-}
-
-static void BarPlayer2TearDown(player2_t player) {
- /* TODO: send final event */
-
- if (player->graph) {
- IGraphBuilder_Release(player->graph);
- player->graph = NULL;
- }
-
- if (player->control) {
- IMediaControl_Release(player->control);
- player->control = NULL;
- }
-
- if (player->event) {
- IMediaEventEx_Release(player->event);
- player->event = NULL;
- }
-
- if (player->audio) {
- IBasicAudio_Release(player->audio);
- player->audio = NULL;
- }
-
- if (player->media) {
- IMediaSeeking_Release(player->media);
- player->media = NULL;
- }
-
- player->state = NO_GRAPH;
-}
-
-static HRESULT BarPlayer2Build(player2_t player) {
- HRESULT hr;
-
- BarPlayer2TearDown(player);
-
- hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, &player->graph);
- if (FAILED(hr))
- return hr;
-
- hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaControl, &player->control);
- if (FAILED(hr))
- return hr;
-
- hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaEventEx, &player->event);
- if (FAILED(hr))
- return hr;
-
- hr = IGraphBuilder_QueryInterface(player->graph, &IID_IBasicAudio, &player->audio);
- if (FAILED(hr))
- return hr;
-
- hr = IGraphBuilder_QueryInterface(player->graph, &IID_IMediaSeeking, &player->media);
- if (FAILED(hr))
- return hr;
-
- hr = IMediaEventEx_SetNotifyWindow(player->event, (OAHWND)NULL, WM_GRAPH_EVENT, (LONG_PTR)player);
- if (FAILED(hr))
- return hr;
-
- player->state = STOPPED;
-
- return S_OK;
-}
-
-static HRESULT BarPlayer2AddFilterByCLSID(IGraphBuilder *pGraph, REFGUID clsid, IBaseFilter **ppF, LPCWSTR wszName) {
- IBaseFilter *pFilter = NULL;
- HRESULT hr;
- *ppF = 0;
-
- hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IBaseFilter, &pFilter);
- if (FAILED(hr))
- goto done;
-
- hr = IGraphBuilder_AddFilter(pGraph, pFilter, wszName);
- if (FAILED(hr))
- goto done;
-
- *ppF = pFilter;
-
- IBaseFilter_AddRef(*ppF);
-
-done:
- if (pFilter)
- IBaseFilter_Release(pFilter);
-
- return hr;
-}
-
-static HRESULT BarPlayer2Render(player2_t player, IBaseFilter* source) {
- BOOL bRenderedAnyPin = FALSE;
-
- IPin* pin = NULL;
- IEnumPins *enumPins = NULL;
- IBaseFilter *audioRenderer = NULL;
- IFilterGraph2* filter = NULL;
-
- HRESULT hr;
-
- hr = IGraphBuilder_QueryInterface(player->graph, &IID_IFilterGraph2, &filter);
- if (FAILED(hr))
- return hr;
-
- hr = BarPlayer2AddFilterByCLSID(player->graph, &CLSID_DSoundRender, &audioRenderer, L"Audio Renderer");
- if (FAILED(hr))
- goto done;
-
- hr = IBaseFilter_EnumPins(source, &enumPins);
- if (FAILED(hr))
- goto done;
-
- while (S_OK == IEnumPins_Next(enumPins, 1, &pin, NULL))
- {
- HRESULT hr2 = IFilterGraph2_RenderEx(filter, pin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
-
- IPin_Release(pin);
- if (SUCCEEDED(hr2))
- bRenderedAnyPin = TRUE;
- }
-
-done:
- if (enumPins)
- IEnumPins_Release(enumPins);
- if (enumPins)
- IBaseFilter_Release(audioRenderer);
- if (enumPins)
- IFilterGraph2_Release(filter);
-
- if (SUCCEEDED(hr) && !bRenderedAnyPin)
- hr = VFW_E_CANNOT_RENDER;
-
- return hr;
-}
-
-bool BarPlayer2Init(player2_t* player) {
-
- if (!BarPlayer2StaticInit ())
- return false;
-
- player2_t out = malloc(sizeof(struct _player_t));
- if (!out)
- return false;
-
- memset(out, 0, sizeof(struct _player_t));
-
- *player = out;
-
- return true;
-}
-
-void BarPlayer2Destroy(player2_t player) {
- BarPlayer2TearDown(player);
- free(player);
-}
-
-void BarPlayer2SetVolume(player2_t player, float volume) {
- player->volume = volume;
- BarPlayer2ApplyVolume(player);
-}
-
-float BarPlayer2GetVolume(player2_t player) {
- return player->volume;
-}
-
-void BarPlayer2SetGain(player2_t player, float gain) {
- player->gain = gain;
- BarPlayer2ApplyVolume(player);
-}
-
-float BarPlayer2GetGain(player2_t player) {
- return player->gain;
-}
-
-double BarPlayer2GetDuration(player2_t player) {
- LONGLONG time;
- if (SUCCEEDED(IMediaSeeking_GetDuration(player->media, &time)))
- return time / 10000000.0;
- else
- return 0;
-}
-
-double BarPlayer2GetTime(player2_t player) {
- LONGLONG time;
- if (SUCCEEDED(IMediaSeeking_GetCurrentPosition(player->media, &time)))
- return time / 10000000.0;
- else
- return 0;
-}
-
-bool BarPlayer2Open(player2_t player, const char* url) {
- IBaseFilter* source = NULL;
- HRESULT hr;
- wchar_t* wideUrl = NULL;
- size_t urlSize;
- int result;
-
- hr = BarPlayer2Build(player);
- if (FAILED(hr))
- goto done;
-
- urlSize = strlen(url);
- result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, NULL, 0);
- wideUrl = malloc((result + 1) * sizeof(wchar_t));
- if (!wideUrl) {
- hr = E_OUTOFMEMORY;
- goto done;
- }
- memset(wideUrl, 0, (result + 1) * sizeof(wchar_t));
-
- MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, urlSize, wideUrl, result);
-
- hr = IGraphBuilder_AddSourceFilter(player->graph, wideUrl, NULL, &source);
- if (FAILED(hr))
- goto done;
-
- hr = BarPlayer2Render(player, source);
-
- BarPlayer2ApplyVolume(player);
-
-done:
- if (wideUrl)
- free(wideUrl);
- if (FAILED(hr))
- BarPlayer2TearDown(player);
- if (source)
- IBaseFilter_Release(source);
-
- return SUCCEEDED(hr);
-}
-
-bool BarPlayer2Play(player2_t player) {
- HRESULT hr;
-
- if (player->state != PAUSED && player->state != STOPPED)
- return false; /* wrong state */
-
- hr = IMediaControl_Run(player->control);
- if (SUCCEEDED(hr))
- player->state = RUNNING;
-
- return SUCCEEDED(hr);
-}
-
-bool BarPlayer2Pause(player2_t player) {
- HRESULT hr;
-
- if (player->state != RUNNING)
- return false; /* wrong state */
-
- hr = IMediaControl_Pause(player->control);
- if (SUCCEEDED(hr))
- player->state = PAUSED;
-
- return SUCCEEDED(hr);
-}
-
-bool BarPlayer2Stop(player2_t player) {
- HRESULT hr;
-
- if (player->state != RUNNING && player->state != PAUSED)
- return false; /* wrong state */
-
- hr = IMediaControl_Stop(player->control);
- if (SUCCEEDED(hr))
- player->state = STOPPED;
-
- return SUCCEEDED(hr);
-}
-
-bool BarPlayer2Finish(player2_t player) {
- if (!player->control)
- return false;
-
- BarPlayer2TearDown(player);
- return true;
-}
-
-bool BarPlayer2IsPlaying(player2_t player) {
- return player->state == RUNNING;
-}
-
-bool BarPlayer2IsPaused(player2_t player) {
- return player->state == PAUSED;
-}
-
-bool BarPlayer2IsStopped(player2_t player) {
- return player->state == STOPPED;
-}
-
-bool BarPlayer2IsFinished(player2_t player) {
- LONGLONG time;
- LONGLONG duration;
-
- if (!player->media || player->state == NO_GRAPH)
- return true;
-
- if (player->state != RUNNING && player->state != STOPPED)
- return false;
-
- if (FAILED(IMediaSeeking_GetDuration(player->media, &duration)) ||
- FAILED(IMediaSeeking_GetCurrentPosition(player->media, &time)))
- return true;
-
- return time >= duration;
-}
diff --git a/src/settings.c b/src/settings.c
index d58eb5c..0765bc8 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -131,6 +131,7 @@ void BarSettingsDestroy (BarSettings_t *settings) {
free (settings->npStationFormat);
free (settings->listSongFormat);
free (settings->titleFormat);
+ free (settings->player);
free (settings->fifo);
free (settings->rpcHost);
free (settings->rpcTlsPort);
@@ -177,6 +178,7 @@ void BarSettingsRead (BarSettings_t *settings) {
settings->npStationFormat = strdup ("Station \"%n\" (%i)");
settings->listSongFormat = strdup ("%i) %a - %t%r");
settings->titleFormat = strdup (TITLE " - \"%t\" by \"%a\" on \"%l\"%r%@%s");
+ settings->player = NULL;
settings->rpcHost = strdup (PIANO_RPC_HOST);
settings->rpcTlsPort = NULL;
settings->partnerUser = strdup ("android");
@@ -332,6 +334,9 @@ void BarSettingsRead (BarSettings_t *settings) {
} else if (streq ("format_title", key)) {
free (settings->titleFormat);
settings->titleFormat = strdup (val);
+ } else if (streq ("player", key)) {
+ free (settings->player);
+ settings->player = strdup (val);
} else if (streq ("fifo", key)) {
free (settings->fifo);
settings->fifo = BarSettingsExpandTilde (val, userhome);
diff --git a/src/settings.h b/src/settings.h
index 8718b34..78d0e45 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -102,6 +102,7 @@ typedef struct {
char *npStationFormat;
char *listSongFormat;
char *titleFormat;
+ char *player;
char *fifo;
char *rpcHost, *rpcTlsPort, *partnerUser, *partnerPassword, *device, *inkey, *outkey, *caBundle;
char keys[BAR_KS_COUNT];
diff --git a/src/ui.h b/src/ui.h
index c9b3d70..705bbab 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -29,7 +29,7 @@ THE SOFTWARE.
#include <piano.h>
#include "settings.h"
-#include "player2.h"
+#include "player/player2.h"
#include "main.h"
#include "ui_readline.h"
#include "ui_types.h"
diff --git a/src/ui_act.c b/src/ui_act.c
index c8a808f..54167f8 100644
--- a/src/ui_act.c
+++ b/src/ui_act.c
@@ -410,9 +410,9 @@ BarUiActCallback(BarUiActPause) {
*/
BarUiActCallback(BarUiActTogglePause) {
if (BarPlayer2IsPlaying(app->player))
- BarPlayer2Play(app->player);
- else
BarPlayer2Pause(app->player);
+ else
+ BarPlayer2Play(app->player);
}
/* rename current station