The native, lightweight bridge between VLC Media Player and your Discord Status. Powered by Windhawk.
Get the lightweight mod manager from windhawk.net.
Open Windhawk and search for 'VLC Discord RPC'.
Click Install. Configure VLC once, and you're done. No restart needed.
Ctrl+P).1234 and Port to 8080.// ==WindhawkMod==
// @id vlc-discord-rpc
// @name VLC Discord Rich Presence
// @description Shows your playing status, quality tags (4K/HDR), and interactive buttons on Discord.
// @version 1.1.0
// @author ciizerr
// @github https://github.com/ciizerr
// @include vlc.exe
// @compilerOptions -lwinhttp
// @architecture x86
// @architecture x86-64
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# VLC Discord Rich Presence
Seamlessly integrates VLC Media Player with Discord to display playback status, media metadata, resolution tags, and **Album Artwork(new)**.
## Features
* **Cover Art Upload:** Automatically uploads local album art and movie posters to `0x0.st` so they appear on Discord.
* **Smart Activity Status:** Dynamically switches between "Listening to **Song**", "Watching **Movie**", or "Playing **Video**" based on the file type.
* **Clean Metadata:**
* **Music:** Displays Song Title, Artist, and Album.
* **Video:** Displays Title, Season/Episode, Chapter, and Audio Language.
* **Quality Tags:** Displays resolution and format tags (4K, HDR, 1080p, 10-bit) based on the media file.
* **Interactive Buttons:** Adds a "Search This" button to your status, redirecting to Google, IMDb, or YouTube.
* **Visual Themes:** Includes options for Default and Dark Mode icon sets.
## Icon Themes
Users can customize the appearance of the Rich Presence icons via the Mod Settings.
* **Default:** The standard orange VLC cone.

* **Dark:** A dark-mode variant for low-light aesthetics.

**Submissions:** We are accepting community designs for new icon themes. If you have created a set (vlc, play, pause, stop), please contact `ciizerr` on Discord.
## Setup Instructions (First Run Only)
For this mod to retrieve data from VLC, the Web Interface must be enabled.
1. Open VLC Media Player.
2. Go to **Tools** > **Preferences** (or press `Ctrl+P`).
3. In the bottom-left corner, under *Show settings*, select **All**.
4. Navigate to **Interface** > **Main interfaces**.
5. On the right panel, check the box for **Web**.

6. In the left sidebar, expand *Main interfaces* and click on **Lua**.
7. Under *Lua HTTP*, set the **Password** to `1234` and **Port** to `8080`.

8. Click **Save** and restart VLC.
## Configuration
**Show Cover Art:** Toggle to enable/disable the uploading of local cover art. If disabled, the mod will use the standard VLC icon.
**Search Provider:** You can change the destination of the search button (Google, Bing, IMDb) in the mod settings.
**Custom Client ID:** Power users who wish to upload their own assets can provide a custom Application ID in the settings.
## Feedback & Support
For bug reports, feature suggestions, or general feedback, please reach out via:
* **Discord:** `ciizerr`
* **GitHub:** [vlc-discord-rpc-archive](https://github.com/ciizerr/vlc-discord-rpc-archive) (contains cross-platform resources of vlc-discord-rpc)
*/
// ==/WindhawkModReadme==
// ==WindhawkModSettings==
/*
- ClientId: "1465711556418474148"
$name: Discord Client ID
$description: "The Application ID from the Discord Developer Portal. Leave default to use the official one."
- ShowCoverArt: true
$name: Show Cover Art
$description: "If enabled, local album art is uploaded to 0x0.st (temp host) to appear on Discord. Disable to use the standard VLC icon."
- Theme: ""
$name: Icon Theme
$description: "Prefix for your assets. Upload images like 'dark_play_icon' to use the Dark theme."
$options:
- "": Default (vlc_icon)
- "dark_": Dark Mode (dark_vlc_icon)
- Provider: Google
$name: Search Provider
$options:
- Google: Google
- Bing: Bing
- IMDb: IMDb
- YouTube: YouTube
- Custom: Custom URL
- CustomUrl: ""
$name: Custom URL
$description: "For other sites (Yahoo, MyAnimeList), enter their search URL here. Example: https://myanimelist.net/search/all?q="
- ButtonLabel: "Search This"
$name: Button Label
$description: "The text displayed on the Discord button (Max 30 chars)."
*/
// ==/WindhawkModSettings==
#include <windows.h>
#include <winhttp.h>
#include <string>
#include <thread>
#include <vector>
#include <cstdio>
#include <atomic>
#include <map>
#include <fstream>
#include <mutex>
#include <algorithm>
// =============================================================
// ⚙️ GLOBALS
// =============================================================
std::atomic<bool> g_stopThread{false};
std::thread g_workerThread;
const std::wstring VLC_PASS_BASE64 = L"OjEyMzQ=";
const std::string SEP = " \xE2\x97\x8F ";
std::map<std::string, std::string> g_imageCache;
std::mutex g_cacheMutex;
// =============================================================
// 1. HELPERS
// =============================================================
std::string WStrToStr(const std::wstring& wstr) {
if (wstr.empty()) return "";
int size = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string str(size, 0);
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &str[0], size, NULL, NULL);
return str;
}
std::wstring StrToWStr(const std::string& str) {
if (str.empty()) return L"";
int size = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
std::wstring wstr(size, 0);
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstr[0], size);
return wstr;
}
std::string UrlEncode(const std::string &value) {
static const char hex[] = "0123456789ABCDEF";
std::string result;
for (char c : value) {
if (isalnum((unsigned char)c) || c == '-' || c == '_' || c == '.' || c == '~') {
result += c;
} else {
result += '%';
result += hex[(c >> 4) & 0xF];
result += hex[c & 0xF];
}
}
return result;
}
std::string UrlDecode(const std::string &value) {
std::string result;
result.reserve(value.length());
for (size_t i = 0; i < value.length(); ++i) {
if (value[i] == '%') {
if (i + 2 < value.length()) {
int hex1 = value[i + 1];
int hex2 = value[i + 2];
hex1 = (hex1 >= '0' && hex1 <= '9') ? (hex1 - '0') : ((hex1 & 0xDF) - 'A' + 10);
hex2 = (hex2 >= '0' && hex2 <= '9') ? (hex2 - '0') : ((hex2 & 0xDF) - 'A' + 10);
result += static_cast<char>((hex1 << 4) | hex2);
i += 2;
}
} else if (value[i] == '+') {
result += ' ';
} else {
result += value[i];
}
}
return result;
}
std::string SanitizeString(const std::string& s) {
std::string out;
for (char c : s) {
if (c == '"') out += '\'';
else if (c == '\\') {}
else if ((unsigned char)c < 32) {}
else out += c;
}
return out;
}
std::string NumToStr(long long num) { return std::to_string(num); }
std::string ExtractString(const std::string& json, const std::string& key) {
std::string search = "\"" + key + "\":\"";
size_t start = json.find(search);
if (start == std::string::npos) return "";
start += search.length();
size_t current = start;
while (current < json.length()) {
size_t nextQuote = json.find("\"", current);
if (nextQuote == std::string::npos) return "";
if (nextQuote > 0 && json[nextQuote - 1] == '\\') {
current = nextQuote + 1;
} else {
return json.substr(start, nextQuote - start);
}
}
return "";
}
long long ExtractNumber(const std::string& json, const std::string& key) {
std::string search = "\"" + key + "\":";
size_t start = json.find(search);
if (start == std::string::npos) return -1;
start += search.length();
if (json[start] == '"') {
start++;
size_t end = json.find("\"", start);
if (end == std::string::npos) return -1;
try { return (long long)std::stod(json.substr(start, end - start)); } catch(...) { return -1; }
}
size_t endComma = json.find(",", start);
size_t endBrace = json.find("}", start);
size_t end = (endComma < endBrace) ? endComma : endBrace;
if (end == std::string::npos) return -1;
try { return (long long)std::stod(json.substr(start, end - start)); } catch(...) { return -1; }
}
std::string CleanString(std::string str) {
std::string out;
for (size_t i = 0; i < str.length(); ++i) {
if (str[i] == '%' && i + 2 < str.length()) {
if (str.substr(i, 3) == "%20") { out += ' '; i += 2; continue; }
if (str.substr(i, 3) == "%5B") { out += '['; i += 2; continue; }
if (str.substr(i, 3) == "%5D") { out += ']'; i += 2; continue; }
}
out += str[i];
}
return out;
}
// =============================================================
// 2. IMAGE UPLOAD LOGIC
// =============================================================
bool ReadFileBytes(const std::wstring& path, std::vector<char>& data) {
std::ifstream file(path.c_str(), std::ios::binary | std::ios::ate);
if (!file) return false;
std::streamsize size = file.tellg();
if (size <= 0 || size > 10 * 1024 * 1024) return false;
file.seekg(0, std::ios::beg);
data.resize(size);
if (!file.read(data.data(), size)) return false;
return true;
}
std::string UploadTo0x0st(const std::string& fileUrl) {
std::string pathStr = UrlDecode(fileUrl);
size_t filePrefix = pathStr.find("file:///");
if (filePrefix != std::string::npos) pathStr = pathStr.substr(filePrefix + 8);
for (auto &c : pathStr) if (c == '/') c = '\\';
{
std::lock_guard<std::mutex> lock(g_cacheMutex);
if (g_imageCache.find(pathStr) != g_imageCache.end()) {
return g_imageCache[pathStr];
}
}
std::vector<char> fileData;
if (!ReadFileBytes(StrToWStr(pathStr), fileData)) return "";
HINTERNET hSession = WinHttpOpen(L"VLC-RPC-Mod/1.3", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) return "";
HINTERNET hConnect = WinHttpConnect(hSession, L"0x0.st", INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect) { WinHttpCloseHandle(hSession); return ""; }
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return ""; }
std::string boundary = "------------------------VlcRpcModBoundary";
std::string header = "Content-Type: multipart/form-data; boundary=" + boundary;
std::string bodyHead;
bodyHead += "--" + boundary + "\r\n";
bodyHead += "Content-Disposition: form-data; name=\"file\"; filename=\"cover.jpg\"\r\n\r\n";
std::string bodyTail;
bodyTail += "\r\n--" + boundary + "--\r\n";
DWORD totalSize = (DWORD)(bodyHead.size() + fileData.size() + bodyTail.size());
bool success = false;
std::string resultUrl = "";
if (WinHttpAddRequestHeaders(hRequest, StrToWStr(header).c_str(), (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD) &&
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, totalSize, 0)) {
DWORD bytesWritten;
WinHttpWriteData(hRequest, bodyHead.c_str(), (DWORD)bodyHead.size(), &bytesWritten);
WinHttpWriteData(hRequest, fileData.data(), (DWORD)fileData.size(), &bytesWritten);
WinHttpWriteData(hRequest, bodyTail.c_str(), (DWORD)bodyTail.size(), &bytesWritten);
if (WinHttpReceiveResponse(hRequest, NULL)) {
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
do {
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) break;
if (dwSize == 0) break;
std::vector<char> respBuf(dwSize + 1);
if (WinHttpReadData(hRequest, respBuf.data(), dwSize, &dwDownloaded)) {
resultUrl.append(respBuf.data(), dwDownloaded);
}
} while (dwSize > 0);
while (!resultUrl.empty() && (resultUrl.back() == '\n' || resultUrl.back() == '\r')) {
resultUrl.pop_back();
}
if (resultUrl.find("http") == 0) success = true;
}
}
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
if (success) {
std::lock_guard<std::mutex> lock(g_cacheMutex);
g_imageCache[pathStr] = resultUrl;
return resultUrl;
}
return "";
}
// =============================================================
// 3. LOGIC HELPERS
// =============================================================
std::string GetAudioLanguages(const std::string& json) {
std::vector<std::string> activeLangs;
std::vector<std::string> allLangs;
for (int i = 0; i < 60; i++) {
std::string streamKey = "\"Stream " + std::to_string(i) + "\":{";
size_t start = json.find(streamKey);
if (start == std::string::npos) continue;
size_t end = json.find("}", start);
if (end == std::string::npos) continue;
std::string block = json.substr(start, end - start);
if (block.find("\"Type\":\"Audio\"") != std::string::npos) {
std::string lang = ExtractString(block, "Language");
if (!lang.empty()) {
std::string shortLang = lang.substr(0, 2);
if (shortLang[0] >= 'a' && shortLang[0] <= 'z') shortLang[0] -= 32;
if (shortLang[1] >= 'a' && shortLang[1] <= 'z') shortLang[1] -= 32;
bool existsAll = false;
for (const auto& l : allLangs) if (l == shortLang) existsAll = true;
if (!existsAll) allLangs.push_back(shortLang);
bool isDecoded = (block.find("Decoded_format") != std::string::npos) ||
(block.find("Decoded_channels") != std::string::npos);
if (isDecoded) {
bool existsActive = false;
for (const auto& l : activeLangs) if (l == shortLang) existsActive = true;
if (!existsActive) activeLangs.push_back(shortLang);
}
}
}
}
std::vector<std::string>* targetList = (activeLangs.size() > 0) ? &activeLangs : &allLangs;
std::string result = "";
for (size_t i = 0; i < targetList->size(); i++) {
if (i > 0) result += " | ";
result += (*targetList)[i];
}
return result;
}
std::string GetQualityTags(const std::string& json) {
std::string tags = "";
for (int i = 0; i < 10; i++) {
std::string streamKey = "\"Stream " + std::to_string(i) + "\":{";
size_t start = json.find(streamKey);
if (start == std::string::npos) continue;
size_t end = json.find("}", start);
if (end == std::string::npos) continue;
std::string block = json.substr(start, end - start);
if (block.find("\"Type\":\"Video\"") != std::string::npos) {
// Resolution
std::string res = ExtractString(block, "Video_resolution");
if (!res.empty()) {
size_t xPos = res.find("x");
if (xPos != std::string::npos) {
try {
long long width = std::stoll(res.substr(0, xPos));
if (width >= 3800) tags = "4K";
else if (width >= 2500) tags = "2K";
else if (width >= 1900) tags = "1080p";
else if (width >= 1200) tags = "720p";
else tags = "SD";
} catch(...) {}
}
}
// HDR Detection
std::string color = ExtractString(block, "Color_primaries");
std::string transfer = ExtractString(block, "Color_transfer_function");
bool isHDR = false;
if (color.find("2020") != std::string::npos) isHDR = true; // BT.2020
if (transfer.find("PQ") != std::string::npos) isHDR = true; // SMPTE ST 2084
if (transfer.find("HLG") != std::string::npos) isHDR = true; // HLG
if (transfer.find("2084") != std::string::npos) isHDR = true;
if (isHDR) {
if (!tags.empty()) tags += SEP;
tags += "HDR";
}
break;
}
}
return tags;
}
std::string GenerateButtonUrl(std::string query, const std::string& provider, const std::string& customUrl) {
std::string base = "";
if (provider == "Google") base = "https://www.google.com/search?q=";
else if (provider == "Bing") base = "https://www.bing.com/search?q=";
else if (provider == "IMDb") base = "https://www.imdb.com/find/?q=";
else if (provider == "YouTube") base = "https://www.youtube.com/results?search_query=";
else if (provider == "Custom") base = customUrl;
if (base.empty()) base = "https://www.google.com/search?q=";
if (query.empty()) query = "VLC Media Player";
return base + UrlEncode(query);
}
int DetectActivityType(const std::string& filename, const std::string& quality) {
if (!quality.empty()) return 3; // Watching
std::string ext = "";
size_t dot = filename.rfind(".");
if (dot != std::string::npos) {
ext = filename.substr(dot);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
}
if (ext == ".mp3" || ext == ".flac" || ext == ".wav" || ext == ".m4a" || ext == ".aac" || ext == ".ogg" || ext == ".wma" || ext == ".opus") {
return 2; // Listening
}
if (ext == ".mkv" || ext == ".mp4" || ext == ".avi" || ext == ".mov" || ext == ".wmv" || ext == ".webm" || ext == ".m4v") {
return 3; // Watching
}
return 0; // Playing
}
// =============================================================
// 4. MAIN WORKER
// =============================================================
void Worker() {
std::string defaultId = "1465711556418474148";
PCWSTR sId = Wh_GetStringSetting(L"ClientId");
std::string myClientId = sId ? WStrToStr(sId) : defaultId;
if (myClientId.empty()) myClientId = defaultId;
Wh_FreeStringSetting(sId);
bool bShowCoverArt = Wh_GetIntSetting(L"ShowCoverArt");
PCWSTR sTheme = Wh_GetStringSetting(L"Theme");
std::string myTheme = sTheme ? WStrToStr(sTheme) : "";
Wh_FreeStringSetting(sTheme);
PCWSTR sProv = Wh_GetStringSetting(L"Provider");
std::string myProvider = sProv ? WStrToStr(sProv) : "Google";
Wh_FreeStringSetting(sProv);
PCWSTR sCust = Wh_GetStringSetting(L"CustomUrl");
std::string myCustomUrl = sCust ? WStrToStr(sCust) : "";
Wh_FreeStringSetting(sCust);
PCWSTR sLbl = Wh_GetStringSetting(L"ButtonLabel");
std::string myBtnLabel = sLbl ? WStrToStr(sLbl) : "Search This";
Wh_FreeStringSetting(sLbl);
myBtnLabel = SanitizeString(myBtnLabel);
std::string assetLarge = myTheme + "vlc_icon";
std::string assetPlay = myTheme + "play_icon";
std::string assetPause = myTheme + "pause_icon";
std::string assetStop = myTheme + "stop_icon";
HINTERNET hSession = WinHttpOpen(L"VLC-RPC/1.3", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
HANDLE hPipe = INVALID_HANDLE_VALUE;
bool isConnected = false;
std::string lastTop = ""; std::string lastBot = ""; bool lastPlaying = false;
std::string lastState = ""; int heartbeat = 0;
long long anchorStart = 0; long long anchorEnd = 0;
int lastActivityType = 0;
std::string lastArtworkLocal = "";
std::string currentRemoteArt = "";
std::string lastDisplayImage = "";
while (!g_stopThread.load()) {
if (hSession && !hConnect) hConnect = WinHttpConnect(hSession, L"127.0.0.1", 8080, 0);
if (hConnect) hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/requests/status.json", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
bool requestSuccess = false;
if (hRequest) {
std::wstring headers = L"Authorization: Basic " + VLC_PASS_BASE64;
if (WinHttpSendRequest(hRequest, headers.c_str(), headers.length(), WINHTTP_NO_REQUEST_DATA, 0, 0, 0) &&
WinHttpReceiveResponse(hRequest, NULL)) {
requestSuccess = true;
std::string json; DWORD dwSize = 0, dwDownloaded = 0;
do {
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) break;
if (dwSize == 0) break;
std::vector<char> buffer(dwSize + 1);
if (WinHttpReadData(hRequest, buffer.data(), dwSize, &dwDownloaded)) {
json.append(buffer.data(), dwDownloaded);
}
} while (dwSize > 0);
if (!json.empty()) {
std::string stateStr = ExtractString(json, "state");
if (stateStr == "stopped") {
if (lastState != "stopped") {
if (!isConnected || hPipe == INVALID_HANDLE_VALUE) {
for (int i=0; i<10; i++) {
std::string name = "\\\\.\\pipe\\discord-ipc-" + std::to_string(i);
hPipe = CreateFileA(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPipe != INVALID_HANDLE_VALUE) break;
}
if (hPipe != INVALID_HANDLE_VALUE) {
std::string hs = "{\"v\":1,\"client_id\":\"" + myClientId + "\"}";
int op=0; int l=(int)hs.length(); DWORD w; WriteFile(hPipe,&op,4,&w,NULL); WriteFile(hPipe,&l,4,&w,NULL); WriteFile(hPipe,hs.c_str(),l,&w,NULL);
isConnected = true;
}
}
if (isConnected) {
std::string js = "{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":" + NumToStr(GetCurrentProcessId()) + ",\"activity\":{";
js += "\"details\":\"Idling\",\"state\":\"Waiting for media...\",\"type\":0,";
js += "\"assets\":{\"large_image\":\"" + assetLarge + "\",\"large_text\":\"VLC Media Player\",\"small_image\":\"" + assetStop + "\",\"small_text\":\"Stopped\"}";
js += "}},\"nonce\":\"1\"}";
int op=1; int l=(int)js.length(); DWORD w; WriteFile(hPipe,&op,4,&w,NULL); WriteFile(hPipe,&l,4,&w,NULL); WriteFile(hPipe,js.c_str(),l,&w,NULL);
}
lastTop = ""; lastState = "stopped";
}
}
else if (stateStr == "playing" || stateStr == "paused") {
std::string filename = CleanString(ExtractString(json, "filename"));
std::string showName = ExtractString(json, "showName");
std::string season = ExtractString(json, "seasonNumber");
std::string episode = ExtractString(json, "episodeNumber");
std::string title = ExtractString(json, "title");
std::string artist = ExtractString(json, "artist");
std::string album = ExtractString(json, "album");
std::string artworkUrl = ExtractString(json, "artwork_url");
long long chapter = ExtractNumber(json, "chapter");
long long time = ExtractNumber(json, "time");
long long length = ExtractNumber(json, "length");
bool isPlaying = (stateStr == "playing");
std::string quality = GetQualityTags(json);
std::string audio = GetAudioLanguages(json);
int activityType = DetectActivityType(filename, quality);
std::string top = ""; std::string bot = ""; std::string query = "";
std::string activityName = "";
std::string largeText = "VLC Media Player";
if (activityType == 2) { // LISTENING
activityName = title.empty() ? filename : title;
top = activityName;
query = activityName + " " + artist;
if (!artist.empty()) {
bot = "by " + artist;
} else if (!album.empty()) {
bot = album;
} else {
bot = "Music";
}
if (!album.empty()) largeText = album;
else largeText = "Listening to Music";
} else { // WATCHING / PLAYING
if (!showName.empty() && !episode.empty()) {
activityName = showName;
top = showName;
if (!quality.empty()) top += SEP + quality;
bot = "S" + season + "E" + episode;
if (chapter >= 0) bot += SEP + "Ch " + NumToStr(chapter + 1);
if (!audio.empty()) bot += SEP + audio;
query = showName + " S" + season + "E" + episode;
largeText = "Watching TV Show";
}
else if (!title.empty()) {
activityName = CleanString(title);
top = CleanString(title);
if (!quality.empty()) top += SEP + quality;
if (chapter >= 0) bot = "Ch " + NumToStr(chapter + 1); else bot = "Video";
if (!audio.empty()) bot += SEP + audio;
query = CleanString(title);
largeText = "Watching Movie";
}
else {
activityName = filename;
top = filename;
if (!quality.empty()) top += SEP + quality;
bot = "Video";
query = filename;
largeText = "Watching Video";
}
}
if (activityName.empty()) activityName = "VLC Media Player";
std::string displayImage = assetLarge;
if (bShowCoverArt && !artworkUrl.empty() && artworkUrl.find("file://") == 0) {
if (artworkUrl != lastArtworkLocal) {
std::string uploaded = UploadTo0x0st(artworkUrl);
if (!uploaded.empty()) {
currentRemoteArt = uploaded;
} else {
currentRemoteArt = "";
}
lastArtworkLocal = artworkUrl;
}
if (!currentRemoteArt.empty()) displayImage = currentRemoteArt;
} else {
displayImage = assetLarge;
}
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
long long cStart = now - (time * 1000);
long long cEnd = cStart + (length * 1000);
long long drift = cStart - anchorStart;
if (drift < 0) drift = -drift;
bool textChg = (top != lastTop || bot != lastBot);
bool stateChg = (isPlaying != lastPlaying);
bool typeChg = (activityType != lastActivityType);
bool artChg = (displayImage != lastDisplayImage);
bool majorDrift = (drift > 3000);
bool force = (heartbeat > 30);
if (textChg || stateChg || typeChg || artChg || majorDrift || force) {
anchorStart = cStart; anchorEnd = cEnd;
if (!isConnected || hPipe == INVALID_HANDLE_VALUE) {
for (int i=0; i<10; i++) {
std::string name = "\\\\.\\pipe\\discord-ipc-" + std::to_string(i);
hPipe = CreateFileA(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPipe != INVALID_HANDLE_VALUE) break;
}
if (hPipe != INVALID_HANDLE_VALUE) {
std::string hs = "{\"v\":1,\"client_id\":\"" + myClientId + "\"}";
int op=0; int l=(int)hs.length(); DWORD w; WriteFile(hPipe,&op,4,&w,NULL); WriteFile(hPipe,&l,4,&w,NULL); WriteFile(hPipe,hs.c_str(),l,&w,NULL);
isConnected = true;
}
}
if (isConnected) {
std::string state = isPlaying ? "Playing" : "Paused";
if (query.empty()) query = "VLC Media Player";
std::string btnUrl = GenerateButtonUrl(query, myProvider, myCustomUrl);
std::string js = "{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":" + NumToStr(GetCurrentProcessId()) + ",\"activity\":{";
js += "\"details\":\"" + SanitizeString(top) + "\",";
js += "\"state\":\"" + SanitizeString(bot) + " (" + state + ")\",";
js += "\"type\":" + NumToStr(activityType) + ",";
js += "\"name\":\"" + SanitizeString(activityName) + "\",";
js += "\"assets\":{\"large_image\":\"" + displayImage + "\",\"large_text\":\"" + SanitizeString(largeText) + "\",\"small_image\":\"" + (isPlaying ? assetPlay : assetPause) + "\",\"small_text\":\"" + state + "\"}";
if (isPlaying && anchorEnd > 0) {
js += ",\"timestamps\":{\"start\":" + NumToStr(anchorStart) + ",\"end\":" + NumToStr(anchorEnd) + "}";
}
js += ",\"buttons\":[{\"label\":\"" + myBtnLabel + "\",\"url\":\"" + btnUrl + "\"}]";
js += "}},\"nonce\":\"1\"}";
int op=1; int l=(int)js.length(); DWORD w;
bool s1 = WriteFile(hPipe,&op,4,&w,NULL);
bool s2 = WriteFile(hPipe,&l,4,&w,NULL);
bool s3 = WriteFile(hPipe,js.c_str(),l,&w,NULL);
if (!s1 || !s2 || !s3) { CloseHandle(hPipe); hPipe = INVALID_HANDLE_VALUE; isConnected = false; }
}
lastTop = top; lastBot = bot; lastPlaying = isPlaying; lastActivityType = activityType;
lastDisplayImage = displayImage;
heartbeat = 0; lastState = stateStr;
} else {
heartbeat++;
}
}
}
}
WinHttpCloseHandle(hRequest); hRequest = NULL;
}
if (!requestSuccess) {
if (hConnect) { WinHttpCloseHandle(hConnect); hConnect = NULL; }
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
} else {
for(int k=0; k<10; k++) {
if (g_stopThread.load()) break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
}
if (isConnected && hPipe != INVALID_HANDLE_VALUE) CloseHandle(hPipe);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
}
BOOL Wh_ModInit() {
g_stopThread = false;
g_workerThread = std::thread(Worker);
return TRUE;
}
void Wh_ModUninit() {
g_stopThread = true;
if (g_workerThread.joinable()) g_workerThread.join();
}
BOOL Wh_ModSettingsChanged(BOOL* bReload) {
*bReload = TRUE;
return TRUE;
}History of updates and improvements.
What's New:
Visual Enhancements
Smarter Status Updates
Contextual Status: Activity status now adapts automatically (new) :
Audio Language: Active audio track language (e.g., English, Japanese) is now shown for video files.
Improvements & Fixes
Improved Layout:
Technical Fixes:
Mod Stability & Core Improvements
This version includes cumulative updates (skipping internal v1.0.1/v1.0.2) to significantly improve stability, error handling, and data safety.
std::atomic<bool> to prevent potential race conditions during thread termination.\") within JSON values, ensuring metadata like "Show Name" or "Episode Title" doesn't break parsing.SanitizeString to strip control characters and convert double quotes to single quotes in Discord Rich Presence details. This prevents Discord IPC errors caused by invalid JSON payloads.Mod Description
This mod seamlessly integrates VLC Media Player with Discord to display rich playback status, media metadata (movies, TV shows, anime), and quality tags (e.g., 4K, HDR).
Key Features
We are accepting icon submissions for upcoming themes! Help us expand the collection with your creative designs.
Everything you need to know about the mod.