From 32be21886cff274fa2d8999d4579984c1e8510ed Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 00:37:50 -0800
Subject: [PATCH 1/9] Events: Re-enable *Changing events

---
 src/eventhandler/EventHandler.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/eventhandler/EventHandler.cpp b/src/eventhandler/EventHandler.cpp
index 42d08b6c..c91b5597 100644
--- a/src/eventhandler/EventHandler.cpp
+++ b/src/eventhandler/EventHandler.cpp
@@ -237,18 +237,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
 			break;
 
 		// Config
-		//case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
-		//	eventHandler->HandleCurrentSceneCollectionChanging();
-		//	break;
+		case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
+			eventHandler->HandleCurrentSceneCollectionChanging();
+			break;
 		case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
 			eventHandler->HandleCurrentSceneCollectionChanged();
 			break;
 		case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
 			eventHandler->HandleSceneCollectionListChanged();
 			break;
-		//case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
-		//	eventHandler->HandleCurrentProfileChanging();
-		//	break;
+		case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
+			eventHandler->HandleCurrentProfileChanging();
+			break;
 		case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
 			eventHandler->HandleCurrentProfileChanged();
 			break;

From 7403264d429a97d44d7ec82ac54204ead1509217 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 01:07:04 -0800
Subject: [PATCH 2/9] Requests/Utils: Modify utils naming/usage

A bit too niche to have a util that takes an output and returns a
timecode string. Let's just make it take a duration in milliseconds.
---
 src/requesthandler/RequestHandler_Record.cpp |  6 ++++--
 src/requesthandler/RequestHandler_Stream.cpp |  6 ++++--
 src/utils/Obs.cpp                            | 10 +---------
 src/utils/Obs.h                              |  2 +-
 4 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/src/requesthandler/RequestHandler_Record.cpp b/src/requesthandler/RequestHandler_Record.cpp
index ef6699f1..db4ffa76 100644
--- a/src/requesthandler/RequestHandler_Record.cpp
+++ b/src/requesthandler/RequestHandler_Record.cpp
@@ -4,11 +4,13 @@ RequestResult RequestHandler::GetRecordStatus(const Request& request)
 {
 	OBSOutputAutoRelease recordOutput = obs_frontend_get_streaming_output();
 
+	uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
+
 	json responseData;
 	responseData["outputActive"] = obs_output_active(recordOutput);
 	responseData["outputPaused"] = obs_output_paused(recordOutput);
-	responseData["outputTimecode"] = Utils::Obs::StringHelper::GetOutputTimecodeString(recordOutput);
-	responseData["outputDuration"] = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
+	responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
+	responseData["outputDuration"] = outputDuration;
 	responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(recordOutput);
 
 	return RequestResult::Success(responseData);
diff --git a/src/requesthandler/RequestHandler_Stream.cpp b/src/requesthandler/RequestHandler_Stream.cpp
index f151aa9f..50b844fe 100644
--- a/src/requesthandler/RequestHandler_Stream.cpp
+++ b/src/requesthandler/RequestHandler_Stream.cpp
@@ -4,11 +4,13 @@ RequestResult RequestHandler::GetStreamStatus(const Request& request)
 {
 	OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
 
+	uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(streamOutput);
+
 	json responseData;
 	responseData["outputActive"] = obs_output_active(streamOutput);
 	responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput);
-	responseData["outputTimecode"] = Utils::Obs::StringHelper::GetOutputTimecodeString(streamOutput);
-	responseData["outputDuration"] = Utils::Obs::NumberHelper::GetOutputDuration(streamOutput);
+	responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
+	responseData["outputDuration"] = outputDuration;
 	responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput);
 	responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput);
 	responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput);
diff --git a/src/utils/Obs.cpp b/src/utils/Obs.cpp
index a4f6c7d4..2edabb7f 100644
--- a/src/utils/Obs.cpp
+++ b/src/utils/Obs.cpp
@@ -144,16 +144,8 @@ std::string Utils::Obs::StringHelper::GetSceneItemBoundsTypeString(enum obs_boun
 	}
 }
 
-std::string Utils::Obs::StringHelper::GetOutputTimecodeString(obs_output_t *output)
+std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
 {
-	if (!output || !obs_output_active(output))
-		return "00:00:00.000";
-
-	video_t* video = obs_output_video(output);
-	uint64_t frameTimeNs = video_output_get_frame_time(video);
-	int totalFrames = obs_output_get_total_frames(output);
-
-	uint64_t ms = util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
 	uint64_t secs = ms / 1000ULL;
 	uint64_t minutes = secs / 60ULL;
 
diff --git a/src/utils/Obs.h b/src/utils/Obs.h
index 8819003f..868a66c1 100644
--- a/src/utils/Obs.h
+++ b/src/utils/Obs.h
@@ -37,7 +37,7 @@ namespace Utils {
 			std::string GetMediaInputStateString(obs_source_t *input);
 			std::string GetLastReplayBufferFilePath();
 			std::string GetSceneItemBoundsTypeString(enum obs_bounds_type type);
-			std::string GetOutputTimecodeString(obs_output_t *output);
+			std::string DurationToTimecode(uint64_t);
 		}
 
 		namespace EnumHelper {

From 08eb2defbc7bfe20de5c052f17922948f90a530f Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 01:17:40 -0800
Subject: [PATCH 3/9] Requests: Add GetMediaInputState

---
 CMakeLists.txt                                |  1 +
 src/requesthandler/RequestHandler.cpp         |  3 +++
 src/requesthandler/RequestHandler.h           |  3 +++
 .../RequestHandler_MediaInputs.cpp            | 21 +++++++++++++++++++
 4 files changed, 28 insertions(+)
 create mode 100644 src/requesthandler/RequestHandler_MediaInputs.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index dc1f5707..0ab45138 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -108,6 +108,7 @@ set(obs-websocket_SOURCES
 	src/requesthandler/RequestHandler_SceneItems.cpp
 	src/requesthandler/RequestHandler_Stream.cpp
 	src/requesthandler/RequestHandler_Record.cpp
+	src/requesthandler/RequestHandler_MediaInputs.cpp
 	src/requesthandler/rpc/Request.cpp
 	src/requesthandler/rpc/RequestResult.cpp
 	src/forms/SettingsDialog.cpp
diff --git a/src/requesthandler/RequestHandler.cpp b/src/requesthandler/RequestHandler.cpp
index 168a7a2a..686692b1 100644
--- a/src/requesthandler/RequestHandler.cpp
+++ b/src/requesthandler/RequestHandler.cpp
@@ -97,6 +97,9 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
 	{"PauseRecord", &RequestHandler::PauseRecord},
 	{"ResumeRecord", &RequestHandler::ResumeRecord},
 	//{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
+
+	// Media Inputs
+	{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
 };
 
 RequestHandler::RequestHandler(SessionPtr session) :
diff --git a/src/requesthandler/RequestHandler.h b/src/requesthandler/RequestHandler.h
index f426e3e4..0f9a0bfb 100644
--- a/src/requesthandler/RequestHandler.h
+++ b/src/requesthandler/RequestHandler.h
@@ -119,6 +119,9 @@ class RequestHandler {
 		RequestResult ResumeRecord(const Request&);
 		RequestResult GetRecordDirectory(const Request&);
 
+		// Media Inputs
+		RequestResult GetMediaInputStatus(const Request&);
+
 		SessionPtr _session;
 		static const std::map<std::string, RequestMethodHandler> _handlerMap;
 };
diff --git a/src/requesthandler/RequestHandler_MediaInputs.cpp b/src/requesthandler/RequestHandler_MediaInputs.cpp
new file mode 100644
index 00000000..076028c6
--- /dev/null
+++ b/src/requesthandler/RequestHandler_MediaInputs.cpp
@@ -0,0 +1,21 @@
+#include "RequestHandler.h"
+
+RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
+{
+	RequestStatus::RequestStatus statusCode;
+	std::string comment;
+	OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
+	if (!input)
+		return RequestResult::Error(statusCode, comment);
+
+	json responseData;
+	responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputStateString(input);
+
+	auto mediaState = obs_source_media_get_state(input);
+	if (mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED) {
+		responseData["mediaDuration"] = obs_source_media_get_duration(input);
+		responseData["mediaCursor"] = obs_source_media_get_time(input);
+	}
+
+	return RequestResult::Success(responseData);
+}

From 0f17d3d6f8fcd68b3061dbfde32b09130507a93c Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 01:23:23 -0800
Subject: [PATCH 4/9] Utils/Obs: Update StringHelper function names

I hated having `String` appended to half of the utils since it was
extra
---
 src/requesthandler/RequestHandler_General.cpp     |  2 +-
 src/requesthandler/RequestHandler_Inputs.cpp      |  2 +-
 src/requesthandler/RequestHandler_MediaInputs.cpp |  2 +-
 src/utils/Obs.cpp                                 | 14 +++++++-------
 src/utils/Obs.h                                   | 10 +++++-----
 5 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/requesthandler/RequestHandler_General.cpp b/src/requesthandler/RequestHandler_General.cpp
index e49d5110..bb631e38 100644
--- a/src/requesthandler/RequestHandler_General.cpp
+++ b/src/requesthandler/RequestHandler_General.cpp
@@ -8,7 +8,7 @@
 RequestResult RequestHandler::GetVersion(const Request& request)
 {
 	json responseData;
-	responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersionString();
+	responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
 	responseData["obsWebSocketVersion"] = OBS_WEBSOCKET_VERSION;
 	responseData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
 	responseData["availableRequests"] = GetRequestList();
diff --git a/src/requesthandler/RequestHandler_Inputs.cpp b/src/requesthandler/RequestHandler_Inputs.cpp
index d95b3daf..e7a49dd7 100644
--- a/src/requesthandler/RequestHandler_Inputs.cpp
+++ b/src/requesthandler/RequestHandler_Inputs.cpp
@@ -319,7 +319,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
 		return RequestResult::Error(statusCode, comment);
 
 	json responseData;
-	responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorTypeString(input);
+	responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
 
 	return RequestResult::Success(responseData);
 }
diff --git a/src/requesthandler/RequestHandler_MediaInputs.cpp b/src/requesthandler/RequestHandler_MediaInputs.cpp
index 076028c6..fc178b65 100644
--- a/src/requesthandler/RequestHandler_MediaInputs.cpp
+++ b/src/requesthandler/RequestHandler_MediaInputs.cpp
@@ -9,7 +9,7 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
 		return RequestResult::Error(statusCode, comment);
 
 	json responseData;
-	responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputStateString(input);
+	responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input);
 
 	auto mediaState = obs_source_media_get_state(input);
 	if (mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED) {
diff --git a/src/utils/Obs.cpp b/src/utils/Obs.cpp
index 2edabb7f..d27c5e9b 100644
--- a/src/utils/Obs.cpp
+++ b/src/utils/Obs.cpp
@@ -30,7 +30,7 @@ std::vector<std::string> ConvertStringArray(char **array)
 	return ret;
 }
 
-std::string Utils::Obs::StringHelper::GetObsVersionString()
+std::string Utils::Obs::StringHelper::GetObsVersion()
 {
 	uint32_t version = obs_get_version();
 
@@ -77,7 +77,7 @@ std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
 	return "";
 }
 
-std::string Utils::Obs::StringHelper::GetSourceTypeString(obs_source_t *source)
+std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
 {
 	obs_source_type sourceType = obs_source_get_type(source);
 
@@ -90,7 +90,7 @@ std::string Utils::Obs::StringHelper::GetSourceTypeString(obs_source_t *source)
 	}
 }
 
-std::string Utils::Obs::StringHelper::GetInputMonitorTypeString(obs_source_t *input)
+std::string Utils::Obs::StringHelper::GetInputMonitorType(obs_source_t *input)
 {
 	obs_monitoring_type monitorType = obs_source_get_monitoring_type(input);
 
@@ -102,7 +102,7 @@ std::string Utils::Obs::StringHelper::GetInputMonitorTypeString(obs_source_t *in
 	}
 }
 
-std::string Utils::Obs::StringHelper::GetMediaInputStateString(obs_source_t *input)
+std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
 {
 	obs_media_state mediaState = obs_source_media_get_state(input);
 
@@ -130,7 +130,7 @@ std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
 	return ret;
 }
 
-std::string Utils::Obs::StringHelper::GetSceneItemBoundsTypeString(enum obs_bounds_type type)
+std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
 {
 	switch (type) {
 		default:
@@ -280,7 +280,7 @@ std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, b
 		if (!enumData->second) {
 			OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
 			item["sourceName"] = obs_source_get_name(itemSource);
-			item["sourceType"] = StringHelper::GetSourceTypeString(itemSource);
+			item["sourceType"] = StringHelper::GetSourceType(itemSource);
 			if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
 				item["inputKind"] = obs_source_get_id(itemSource);
 			else if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
@@ -425,7 +425,7 @@ json Utils::Obs::DataHelper::GetSceneItemTransform(obs_sceneitem_t *item)
 
 	ret["alignment"] = osi.alignment;
 
-	ret["boundsType"] = StringHelper::GetSceneItemBoundsTypeString(osi.bounds_type);
+	ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type);
 	ret["boundsAlignment"] = osi.bounds_alignment;
 	ret["boundsWidth"] = osi.bounds.x;
 	ret["boundsHeight"] = osi.bounds.y;
diff --git a/src/utils/Obs.h b/src/utils/Obs.h
index 868a66c1..082dc448 100644
--- a/src/utils/Obs.h
+++ b/src/utils/Obs.h
@@ -27,16 +27,16 @@ enum ObsMediaInputAction {
 namespace Utils {
 	namespace Obs {
 		namespace StringHelper {
-			std::string GetObsVersionString();
+			std::string GetObsVersion();
 			std::string GetCurrentSceneCollection();
 			std::string GetCurrentProfile();
 			std::string GetCurrentProfilePath();
 			std::string GetCurrentRecordOutputPath();
-			std::string GetSourceTypeString(obs_source_t *source);
-			std::string GetInputMonitorTypeString(obs_source_t *input);
-			std::string GetMediaInputStateString(obs_source_t *input);
+			std::string GetSourceType(obs_source_t *source);
+			std::string GetInputMonitorType(obs_source_t *input);
+			std::string GetMediaInputState(obs_source_t *input);
 			std::string GetLastReplayBufferFilePath();
-			std::string GetSceneItemBoundsTypeString(enum obs_bounds_type type);
+			std::string GetSceneItemBoundsType(enum obs_bounds_type type);
 			std::string DurationToTimecode(uint64_t);
 		}
 

From 32e4ad74b7b55bdc368c990825ac4f01e3225306 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 01:32:37 -0800
Subject: [PATCH 5/9] CONTRIBUTING: Update standards

Update some links and standards with what we've been using
---
 CONTRIBUTING.md | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 628b16ef..3c68be97 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,21 +29,20 @@ These are required to automatically generate the [protocol specification documen
 
 * Favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
 ```cpp
-if (success) {
+if (success)
     return RequestResult::Success();
-} else {
+else
     return RequestResult::Error(RequestStatus::GenericError);
-}
 ```
 is better like this:
 ```cpp
-if (!success) {
+if (!success)
     return RequestResult::Error(RequestStatus::GenericError);
-}
+
 return RequestResult::Success();
 ```
 
-* Try to use the [built-in](https://github.com/Palakis/obs-websocket/blob/master/src/requesthandler/rpc/Request.h) request checks when possible.
+* Try to use the [built-in](https://github.com/obs-websocket/obs-websocket/blob/master/src/requesthandler/rpc/Request.h) request checks when possible.
     * Refer to existing requests for usage examples.
 
 * Some example common response/request property names are:
@@ -54,12 +53,14 @@ return RequestResult::Success();
 
 * Response parameters which have no attributed data due to an invalid state should be set to `null` (versus being left out)
     * For example, when `GetSceneList` is called and OBS is not in studio mode, `currentPreviewSceneName` will be `null`
-    * If a request's core response data depends on a state, an error should be thrown unless `ignoreNonFatalRequestChecks` is set. See `GetCurrentPreviewScene` as an example.
+    * If a request's core response data depends on a state, an error should be thrown. See `GetCurrentPreviewScene` as an example.
+
+* In general, try to match the style of existing code as best as possible. We try our best to keep a consistent code style, and may suggest nitpicks as necessary.
 
 ### Commit Guidelines
 
 * Commits follow the 50/72 standard:
-    * 50 characters max for the commit title (excluding scope name)
+    * 50 characters suggested max for the commit title (absolute maximum 72 including scope)
     * One empty line after the title
     * Description wrapped to 72 columns max width per line.
 

From 061fb6f01209111488f5e7c2b656651f9897f394 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 01:37:37 -0800
Subject: [PATCH 6/9] Base: Update various links

Now that obs-websocket has been moved to an org, let's update
any links accordingly.
---
 .github/pull_request_template.md   | 4 ++--
 BUILDING.md                        | 6 +++---
 README.md                          | 6 +++---
 installer/installer-windows.iss.in | 2 +-
 4 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 62ce5a59..5ad3fe5e 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,6 +1,6 @@
 <!--- Please fill out the following template, which will help other contributors review your Pull Request. -->
 
-<!--- Make sure you’ve read the contribution guidelines here: https://github.com/Palakis/obs-websocket/blob/master/CONTRIBUTING.md -->
+<!--- Make sure you’ve read the contribution guidelines here: https://github.com/obs-websocket/obs-websocket/blob/master/CONTRIBUTING.md -->
 
 ### Description
 <!--- Describe your changes. -->
@@ -27,7 +27,7 @@ Tested OS(s):
 ### Checklist:
 <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
 <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
--  [ ] I have read the [**contributing** document](https://github.com/Palakis/obs-websocket/blob/4.x-current/CONTRIBUTING.md).
+-  [ ] I have read the [**contributing** document](https://github.com/obs-websocket/obs-websocket/blob/4.x-current/CONTRIBUTING.md).
 -  [ ] My code is not on the master branch.
 -  [ ] The code has been tested.
 -  [ ] All commit messages are properly formatted and commits squashed where appropriate.
diff --git a/BUILDING.md b/BUILDING.md
index 9dda2fed..a7f25241 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -21,7 +21,7 @@ On Debian/Ubuntu :
 
 ```shell
 sudo apt-get install libboost-all-dev
-git clone --recursive https://github.com/Palakis/obs-websocket.git
+git clone --recursive https://github.com/obs-websocket/obs-websocket.git
 cd obs-websocket
 mkdir build && cd build
 cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
@@ -51,7 +51,7 @@ Of course, you're encouraged to dig through the contents of these scripts to
 look for issues or specificities.
 
 ```shell
-git clone --recursive https://github.com/Palakis/obs-websocket.git
+git clone --recursive https://github.com/obs-websocket/obs-websocket.git
 cd obs-websocket
 ./CI/install-dependencies-macos.sh
 ./CI/install-build-obs-macos.sh
@@ -63,4 +63,4 @@ This will result in a ready-to-use `obs-websocket.pkg` installer in the `release
 
 ## Automated Builds
 
-![Github Actions](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)
+![Github Actions](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)
diff --git a/README.md b/README.md
index fc47efe6..a2cbe09e 100644
--- a/README.md
+++ b/README.md
@@ -12,13 +12,13 @@
 
 WebSockets API for OBS Studio.
 
-[![CI Multiplatform Build](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml)
+[![CI Multiplatform Build](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml)
 [![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A)
 [![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket)
 
 ## Downloads
 
-Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
+Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/obs-websocket/obs-websocket/releases) section.
 
 ### Homebrew
 
@@ -75,7 +75,7 @@ Please join the localization project on [Crowdin](https://crowdin.com/project/ob
 ### Code Contributors
 
 This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
-<a href="https://github.com/Palakis/obs-websocket/graphs/contributors"><img src="https://opencollective.com/obs-websocket/contributors.svg?width=890&button=false" /></a>
+<a href="https://github.com/obs-websocket/obs-websocket/graphs/contributors"><img src="https://opencollective.com/obs-websocket/contributors.svg?width=890&button=false" /></a>
 
 ### Financial Contributors
 
diff --git a/installer/installer-windows.iss.in b/installer/installer-windows.iss.in
index 2990ccdb..f27c99cf 100644
--- a/installer/installer-windows.iss.in
+++ b/installer/installer-windows.iss.in
@@ -1,7 +1,7 @@
 #define MyAppName "obs-websocket"
 #define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
 #define MyAppPublisher "Stephane Lepin"
-#define MyAppURL "http://github.com/Palakis/obs-websocket"
+#define MyAppURL "http://github.com/obs-websocket/obs-websocket"
 
 [Setup]
 ; NOTE: The value of AppId uniquely identifies this application.

From 1dbb7a968676b85d5b5f00d621ffb097d8983d13 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 02:09:27 -0800
Subject: [PATCH 7/9] Requests: Apply some code style changes

---
 src/requesthandler/RequestHandler_Config.cpp      | 6 +++---
 src/requesthandler/RequestHandler_Inputs.cpp      | 2 ++
 src/requesthandler/RequestHandler_MediaInputs.cpp | 3 +++
 src/requesthandler/RequestHandler_Scenes.cpp      | 4 ++--
 src/utils/Obs.cpp                                 | 6 +++++-
 5 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/src/requesthandler/RequestHandler_Config.cpp b/src/requesthandler/RequestHandler_Config.cpp
index cee2447c..dd26c63e 100644
--- a/src/requesthandler/RequestHandler_Config.cpp
+++ b/src/requesthandler/RequestHandler_Config.cpp
@@ -208,10 +208,10 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
 		responseData["defaultParameterValue"] = config_get_default_string(profile, parameterCategory.c_str(), parameterName.c_str());
 	} else if (config_has_user_value(profile, parameterCategory.c_str(), parameterName.c_str())) {
 		responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str());
-		responseData["defaultParameterValue"] = nullptr;
+		responseData["defaultParameterValue"] = json::null();
 	} else {
-		responseData["parameterValue"] = nullptr;
-		responseData["defaultParameterValue"] = nullptr;
+		responseData["parameterValue"] = json::null();
+		responseData["defaultParameterValue"] = json::null();
 	}
 
 	return RequestResult::Success(responseData);
diff --git a/src/requesthandler/RequestHandler_Inputs.cpp b/src/requesthandler/RequestHandler_Inputs.cpp
index e7a49dd7..89f99d68 100644
--- a/src/requesthandler/RequestHandler_Inputs.cpp
+++ b/src/requesthandler/RequestHandler_Inputs.cpp
@@ -365,6 +365,8 @@ std::vector<json> GetListPropertyItems(obs_property_t *property)
 			itemData["itemValue"] = obs_property_list_item_float(property, i);
 		} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
 			itemData["itemValue"] = obs_property_list_item_string(property, i);
+		} else {
+			itemData["itemValue"] = json::null();
 		}
 		ret.push_back(itemData);
 	}
diff --git a/src/requesthandler/RequestHandler_MediaInputs.cpp b/src/requesthandler/RequestHandler_MediaInputs.cpp
index fc178b65..0fb8ff64 100644
--- a/src/requesthandler/RequestHandler_MediaInputs.cpp
+++ b/src/requesthandler/RequestHandler_MediaInputs.cpp
@@ -15,6 +15,9 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
 	if (mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED) {
 		responseData["mediaDuration"] = obs_source_media_get_duration(input);
 		responseData["mediaCursor"] = obs_source_media_get_time(input);
+	} else {
+		responseData["mediaDuration"] = json::null();
+		responseData["mediaCursor"] = json::null();
 	}
 
 	return RequestResult::Success(responseData);
diff --git a/src/requesthandler/RequestHandler_Scenes.cpp b/src/requesthandler/RequestHandler_Scenes.cpp
index 9c69fcc4..6ad16514 100644
--- a/src/requesthandler/RequestHandler_Scenes.cpp
+++ b/src/requesthandler/RequestHandler_Scenes.cpp
@@ -8,13 +8,13 @@ RequestResult RequestHandler::GetSceneList(const Request& request)
 	if (currentProgramScene)
 		responseData["currentProgramSceneName"] = obs_source_get_name(currentProgramScene);
 	else
-		responseData["currentProgramSceneName"] = nullptr;
+		responseData["currentProgramSceneName"] = json::null();
 
 	OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene();
 	if (currentPreviewScene)
 		responseData["currentPreviewSceneName"] = obs_source_get_name(currentPreviewScene);
 	else
-		responseData["currentPreviewSceneName"] = nullptr;
+		responseData["currentPreviewSceneName"] = json::null();
 
 	responseData["scenes"] = Utils::Obs::ListHelper::GetSceneList();
 
diff --git a/src/utils/Obs.cpp b/src/utils/Obs.cpp
index d27c5e9b..96365299 100644
--- a/src/utils/Obs.cpp
+++ b/src/utils/Obs.cpp
@@ -283,8 +283,12 @@ std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, b
 			item["sourceType"] = StringHelper::GetSourceType(itemSource);
 			if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
 				item["inputKind"] = obs_source_get_id(itemSource);
-			else if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
+			else
+				item["inputKind"] = json::null();
+			if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
 				item["isGroup"] = obs_source_is_group(itemSource);
+			else
+				item["isGroup"] = json::null();
 		}
 
 		enumData->first.push_back(item);

From 01013c1b2782ffc0470784516475e3e5cd049772 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 02:58:11 -0800
Subject: [PATCH 8/9] Base: Fix compile error

I literally could have sworn I remember json::null() being valid, but
I guess not. Oops
---
 src/requesthandler/RequestHandler_Config.cpp | 14 +++++++-------
 src/requesthandler/RequestHandler_Inputs.cpp |  2 +-
 src/requesthandler/RequestHandler_Scenes.cpp |  4 ++--
 src/utils/Obs.cpp                            | 16 ++++++++++++++--
 src/utils/Obs.h                              |  6 ++++--
 5 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/src/requesthandler/RequestHandler_Config.cpp b/src/requesthandler/RequestHandler_Config.cpp
index dd26c63e..10692cd3 100644
--- a/src/requesthandler/RequestHandler_Config.cpp
+++ b/src/requesthandler/RequestHandler_Config.cpp
@@ -23,10 +23,10 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
 
 	json responseData;
 	json persistentData;
-	if (!(Utils::Json::GetJsonFileContent(persistentDataPath, persistentData) && persistentData.contains(slotName)))
-		responseData["slotValue"] = nullptr;
-	else
+	if (Utils::Json::GetJsonFileContent(persistentDataPath, persistentData) && persistentData.contains(slotName))
 		responseData["slotValue"] = persistentData[slotName];
+	else
+		responseData["slotValue"] = nullptr;
 
 	return RequestResult::Success(responseData);
 }
@@ -208,10 +208,10 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
 		responseData["defaultParameterValue"] = config_get_default_string(profile, parameterCategory.c_str(), parameterName.c_str());
 	} else if (config_has_user_value(profile, parameterCategory.c_str(), parameterName.c_str())) {
 		responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str());
-		responseData["defaultParameterValue"] = json::null();
+		responseData["defaultParameterValue"] = nullptr;
 	} else {
-		responseData["parameterValue"] = json::null();
-		responseData["defaultParameterValue"] = json::null();
+		responseData["parameterValue"] = nullptr;
+		responseData["defaultParameterValue"] = nullptr;
 	}
 
 	return RequestResult::Success(responseData);
@@ -347,7 +347,7 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
 		obs_service_update(currentStreamService, newStreamServiceSettings);
 	} else {
 		// TODO: This leaks memory. I have no idea why.
-		OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, NULL);
+		OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, nullptr);
 		// TODO: Check service type here, instead of relying on service creation to fail.
 		if (!newStreamService)
 			return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the stream service with the requested streamServiceType. It may be an invalid type.");
diff --git a/src/requesthandler/RequestHandler_Inputs.cpp b/src/requesthandler/RequestHandler_Inputs.cpp
index 89f99d68..95c982c7 100644
--- a/src/requesthandler/RequestHandler_Inputs.cpp
+++ b/src/requesthandler/RequestHandler_Inputs.cpp
@@ -366,7 +366,7 @@ std::vector<json> GetListPropertyItems(obs_property_t *property)
 		} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
 			itemData["itemValue"] = obs_property_list_item_string(property, i);
 		} else {
-			itemData["itemValue"] = json::null();
+			itemData["itemValue"] = nullptr;
 		}
 		ret.push_back(itemData);
 	}
diff --git a/src/requesthandler/RequestHandler_Scenes.cpp b/src/requesthandler/RequestHandler_Scenes.cpp
index 6ad16514..9c69fcc4 100644
--- a/src/requesthandler/RequestHandler_Scenes.cpp
+++ b/src/requesthandler/RequestHandler_Scenes.cpp
@@ -8,13 +8,13 @@ RequestResult RequestHandler::GetSceneList(const Request& request)
 	if (currentProgramScene)
 		responseData["currentProgramSceneName"] = obs_source_get_name(currentProgramScene);
 	else
-		responseData["currentProgramSceneName"] = json::null();
+		responseData["currentProgramSceneName"] = nullptr;
 
 	OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene();
 	if (currentPreviewScene)
 		responseData["currentPreviewSceneName"] = obs_source_get_name(currentPreviewScene);
 	else
-		responseData["currentPreviewSceneName"] = json::null();
+		responseData["currentPreviewSceneName"] = nullptr;
 
 	responseData["scenes"] = Utils::Obs::ListHelper::GetSceneList();
 
diff --git a/src/utils/Obs.cpp b/src/utils/Obs.cpp
index 96365299..a5a5b7eb 100644
--- a/src/utils/Obs.cpp
+++ b/src/utils/Obs.cpp
@@ -171,6 +171,18 @@ enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string
 	return OBS_BOUNDS_NONE;
 }
 
+enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
+{
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY);
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE);
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP);
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART);
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT);
+	RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
+
+	return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
+}
+
 uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
 {
 	if (!output || !obs_output_active(output))
@@ -284,11 +296,11 @@ std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, b
 			if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
 				item["inputKind"] = obs_source_get_id(itemSource);
 			else
-				item["inputKind"] = json::null();
+				item["inputKind"] = nullptr;
 			if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
 				item["isGroup"] = obs_source_is_group(itemSource);
 			else
-				item["isGroup"] = json::null();
+				item["isGroup"] = nullptr;
 		}
 
 		enumData->first.push_back(item);
diff --git a/src/utils/Obs.h b/src/utils/Obs.h
index 082dc448..883c2160 100644
--- a/src/utils/Obs.h
+++ b/src/utils/Obs.h
@@ -16,10 +16,11 @@ enum ObsOutputState {
 };
 
 enum ObsMediaInputAction {
-	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE,
+	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE,
 	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY,
-	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART,
+	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE,
 	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP,
+	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART,
 	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT,
 	OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS
 };
@@ -42,6 +43,7 @@ namespace Utils {
 
 		namespace EnumHelper {
 			enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
+			enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
 		}
 
 		namespace NumberHelper {

From 4076c0baa9cd4a72d06cd324185b966941d4ac24 Mon Sep 17 00:00:00 2001
From: tt2468 <tt2468@gmail.com>
Date: Wed, 17 Nov 2021 03:03:14 -0800
Subject: [PATCH 9/9] Requests: Add media input requests

---
 src/requesthandler/RequestHandler.cpp         |  3 +
 src/requesthandler/RequestHandler.h           |  3 +
 .../RequestHandler_MediaInputs.cpp            | 94 ++++++++++++++++++-
 3 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/src/requesthandler/RequestHandler.cpp b/src/requesthandler/RequestHandler.cpp
index 686692b1..4def32ee 100644
--- a/src/requesthandler/RequestHandler.cpp
+++ b/src/requesthandler/RequestHandler.cpp
@@ -100,6 +100,9 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
 
 	// Media Inputs
 	{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
+	{"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor},
+	{"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor},
+	{"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction},
 };
 
 RequestHandler::RequestHandler(SessionPtr session) :
diff --git a/src/requesthandler/RequestHandler.h b/src/requesthandler/RequestHandler.h
index 0f9a0bfb..4c057c15 100644
--- a/src/requesthandler/RequestHandler.h
+++ b/src/requesthandler/RequestHandler.h
@@ -121,6 +121,9 @@ class RequestHandler {
 
 		// Media Inputs
 		RequestResult GetMediaInputStatus(const Request&);
+		RequestResult SetMediaInputCursor(const Request&);
+		RequestResult OffsetMediaInputCursor(const Request&);
+		RequestResult TriggerMediaInputAction(const Request&);
 
 		SessionPtr _session;
 		static const std::map<std::string, RequestMethodHandler> _handlerMap;
diff --git a/src/requesthandler/RequestHandler_MediaInputs.cpp b/src/requesthandler/RequestHandler_MediaInputs.cpp
index 0fb8ff64..3e1b83dd 100644
--- a/src/requesthandler/RequestHandler_MediaInputs.cpp
+++ b/src/requesthandler/RequestHandler_MediaInputs.cpp
@@ -1,5 +1,11 @@
 #include "RequestHandler.h"
 
+bool IsMediaTimeValid(obs_source_t *input)
+{
+	auto mediaState = obs_source_media_get_state(input);
+	return mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED;
+}
+
 RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
 {
 	RequestStatus::RequestStatus statusCode;
@@ -11,14 +17,94 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
 	json responseData;
 	responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input);
 
-	auto mediaState = obs_source_media_get_state(input);
-	if (mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED) {
+	if (IsMediaTimeValid(input)) {
 		responseData["mediaDuration"] = obs_source_media_get_duration(input);
 		responseData["mediaCursor"] = obs_source_media_get_time(input);
 	} else {
-		responseData["mediaDuration"] = json::null();
-		responseData["mediaCursor"] = json::null();
+		responseData["mediaDuration"] = nullptr;
+		responseData["mediaCursor"] = nullptr;
 	}
 
 	return RequestResult::Success(responseData);
 }
+
+RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
+{
+	RequestStatus::RequestStatus statusCode;
+	std::string comment;
+	OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
+	if (!(input && request.ValidateNumber("mediaCursor", statusCode, comment, 0)))
+		return RequestResult::Error(statusCode, comment);
+
+	if (!IsMediaTimeValid(input))
+		return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
+
+	int64_t mediaCursor = request.RequestData["mediaCursor"];
+
+	// Yes, we're setting the time without checking if it's valid. Can't baby everything.
+	obs_source_media_set_time(input, mediaCursor);
+
+	return RequestResult::Success();
+}
+
+RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
+{
+	RequestStatus::RequestStatus statusCode;
+	std::string comment;
+	OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
+	if (!(input && request.ValidateNumber("mediaCursorOffset", statusCode, comment)))
+		return RequestResult::Error(statusCode, comment);
+
+	if (!IsMediaTimeValid(input))
+		return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
+
+	int64_t mediaCursorOffset = request.RequestData["mediaCursorOffset"];
+	int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset;
+
+	if (mediaCursor < 0)
+		mediaCursor = 0;
+
+	obs_source_media_set_time(input, mediaCursor);
+
+	return RequestResult::Success();
+}
+
+RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
+{
+	RequestStatus::RequestStatus statusCode;
+	std::string comment;
+	OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
+	if (!(input && request.ValidateString("mediaAction", statusCode, comment)))
+		return RequestResult::Error(statusCode, comment);
+
+	std::string mediaActionString = request.RequestData["mediaAction"];
+	auto mediaAction = Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString);
+
+	switch (mediaAction) {
+		default:
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
+			return RequestResult::Error(RequestStatus::InvalidRequestParameter, "You have specified an invalid media input action.");
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
+			// Shoutout to whoever implemented this API call like this
+			obs_source_media_play_pause(input, false);
+			break;
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
+			obs_source_media_play_pause(input, true);
+			break;
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
+			obs_source_media_stop(input);
+			break;
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
+			// I'm only implementing this because I'm nice. I think its a really dumb action.
+			obs_source_media_restart(input);
+			break;
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
+			obs_source_media_next(input);
+			break;
+		case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
+			obs_source_media_previous(input);
+			break;
+	}
+
+	return RequestResult::Success();
+}