diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.h | 2 | ||||
-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.c | 2 | ||||
-rw-r--r-- | src/main.c | 11 | ||||
-rw-r--r-- | src/main.h | 4 | ||||
-rw-r--r-- | src/player/backends/direct_show.c | 464 | ||||
-rw-r--r-- | src/player/backends/media_foundation.cpp | 1110 | ||||
-rw-r--r-- | src/player/backends/media_foundation.h | 115 | ||||
-rw-r--r-- | src/player/backends/utility/com_ptr.h | 148 | ||||
-rw-r--r-- | src/player/backends/utility/optional.h | 517 | ||||
-rw-r--r-- | src/player/player2.c | 211 | ||||
-rw-r--r-- | src/player/player2.h (renamed from src/player2.h) | 45 | ||||
-rw-r--r-- | src/player/player2_private.h | 35 | ||||
-rw-r--r-- | src/player2.c | 409 | ||||
-rw-r--r-- | src/settings.c | 5 | ||||
-rw-r--r-- | src/settings.h | 1 | ||||
-rw-r--r-- | src/ui.h | 2 | ||||
-rw-r--r-- | src/ui_act.c | 4 |
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> @@ -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, @@ -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]; @@ -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 |