diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 0e0fac7..6d90b97 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,6 +14,9 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Checkout submodules
+ run: git submodule update --init --recursive
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
@@ -36,6 +39,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
+ - name: Checkout submodules
+ run: git submodule update --init --recursive
+
- name: Setup .NET on Windows
uses: actions/setup-dotnet@v4
with:
@@ -63,6 +69,9 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Checkout submodules
+ run: git submodule update --init --recursive
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
diff --git a/.gitignore b/.gitignore
index 409abca..acb39a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@ Plugins/UnrealEngine/BocchiTracker/Intermediate
Plugins/UnrealEngine/ThirdParty/flatbuffers/include
Plugins/UnrealEngine/ThirdParty/flatbuffers/lib
Google.FlatBuffers.*
+websocket-sharp.*
+
### Unity ###
# This .gitignore file should be placed at the root of your Unity project directory
diff --git a/.gitmodules b/.gitmodules
index 0c2d5c6..8ba8bb8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "ExternalTools/godot-cpp"]
path = ExternalTools/godot-cpp
url = https://github.com/godotengine/godot-cpp
+[submodule "ExternalTools/websocket-sharp"]
+ path = ExternalTools/websocket-sharp
+ url = https://github.com/sta/websocket-sharp
diff --git a/Application/BocchiTracker.WPF.sln b/Application/BocchiTracker.WPF.sln
index 90f27e2..d16bf52 100644
--- a/Application/BocchiTracker.WPF.sln
+++ b/Application/BocchiTracker.WPF.sln
@@ -28,6 +28,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProcessLinkQuery", "Models\ProcessLinkQuery\ProcessLinkQuery.csproj", "{98D7A38A-C3D8-4047-9172-674317FE78CF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BocchiTracker.Client", "WPF\BocchiTracker.Client\BocchiTracker.Client.csproj", "{949ED95B-18C1-43B3-984D-9144D967C57B}"
+ ProjectSection(ProjectDependencies) = postProject
+ {ADEDB772-6FFB-4198-9107-1AD791899679} = {ADEDB772-6FFB-4198-9107-1AD791899679}
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WPF", "WPF", "{12AAC0C3-970D-43F6-BD47-0B662B7157F6}"
EndProject
@@ -43,82 +46,173 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceClientData", "Models
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BocchiTracker.Client.Share", "WPF\BocchiTracker.Client.Share\BocchiTracker.Client.Share.csproj", "{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessorAsync", "Models\ImageProcessorAsync\ImageProcessorAsync.csproj", "{ADB878D3-01EC-4FFD-94D9-4348E1689605}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCaptureRTC", "Models\GameCaptureRTC\GameCaptureRTC.csproj", "{ADEDB772-6FFB-4198-9107-1AD791899679}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BocchiTracker.WebRTCTest", "Tests\BocchiTracker.WebRTCTest\BocchiTracker.WebRTCTest.csproj", "{B3675F7D-DF3F-4F0E-A9E0-9575381BC964}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExternalTools", "ExternalTools", "{75B25179-1B35-461D-80AA-80EF52E7E101}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "websocket-sharp", "..\ExternalTools\websocket-sharp\websocket-sharp\websocket-sharp.csproj", "{0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{54957DCE-658B-4057-82DF-1AE2679DFCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54957DCE-658B-4057-82DF-1AE2679DFCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54957DCE-658B-4057-82DF-1AE2679DFCEB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {54957DCE-658B-4057-82DF-1AE2679DFCEB}.Debug|x64.Build.0 = Debug|Any CPU
{54957DCE-658B-4057-82DF-1AE2679DFCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54957DCE-658B-4057-82DF-1AE2679DFCEB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54957DCE-658B-4057-82DF-1AE2679DFCEB}.Release|x64.ActiveCfg = Release|Any CPU
+ {54957DCE-658B-4057-82DF-1AE2679DFCEB}.Release|x64.Build.0 = Release|Any CPU
{1F43C496-63EF-41D8-ADA0-39862677302E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F43C496-63EF-41D8-ADA0-39862677302E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F43C496-63EF-41D8-ADA0-39862677302E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1F43C496-63EF-41D8-ADA0-39862677302E}.Debug|x64.Build.0 = Debug|Any CPU
{1F43C496-63EF-41D8-ADA0-39862677302E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F43C496-63EF-41D8-ADA0-39862677302E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F43C496-63EF-41D8-ADA0-39862677302E}.Release|x64.ActiveCfg = Release|Any CPU
+ {1F43C496-63EF-41D8-ADA0-39862677302E}.Release|x64.Build.0 = Release|Any CPU
{ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Debug|x64.Build.0 = Debug|Any CPU
{ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Release|x64.ActiveCfg = Release|Any CPU
+ {ECE3A759-0580-47DC-8FD9-17F3A987DA67}.Release|x64.Build.0 = Release|Any CPU
{6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Debug|x64.Build.0 = Debug|Any CPU
{6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Release|x64.ActiveCfg = Release|Any CPU
+ {6E5DBE53-A122-4CF3-8881-3223C2918F2A}.Release|x64.Build.0 = Release|Any CPU
{7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Debug|x64.Build.0 = Debug|Any CPU
{7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Release|x64.ActiveCfg = Release|Any CPU
+ {7F470DCB-EC9D-44A5-9E66-FAD44223951E}.Release|x64.Build.0 = Release|Any CPU
{CA925BF6-4D4A-450E-98FC-F54B06662E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA925BF6-4D4A-450E-98FC-F54B06662E54}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA925BF6-4D4A-450E-98FC-F54B06662E54}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CA925BF6-4D4A-450E-98FC-F54B06662E54}.Debug|x64.Build.0 = Debug|Any CPU
{CA925BF6-4D4A-450E-98FC-F54B06662E54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA925BF6-4D4A-450E-98FC-F54B06662E54}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA925BF6-4D4A-450E-98FC-F54B06662E54}.Release|x64.ActiveCfg = Release|Any CPU
+ {CA925BF6-4D4A-450E-98FC-F54B06662E54}.Release|x64.Build.0 = Release|Any CPU
{FE19C801-2DB0-4166-9516-9F0C40A206AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE19C801-2DB0-4166-9516-9F0C40A206AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE19C801-2DB0-4166-9516-9F0C40A206AF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FE19C801-2DB0-4166-9516-9F0C40A206AF}.Debug|x64.Build.0 = Debug|Any CPU
{FE19C801-2DB0-4166-9516-9F0C40A206AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE19C801-2DB0-4166-9516-9F0C40A206AF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE19C801-2DB0-4166-9516-9F0C40A206AF}.Release|x64.ActiveCfg = Release|Any CPU
+ {FE19C801-2DB0-4166-9516-9F0C40A206AF}.Release|x64.Build.0 = Release|Any CPU
{B9CF3E8D-4599-40C6-9750-73783203AD2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9CF3E8D-4599-40C6-9750-73783203AD2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9CF3E8D-4599-40C6-9750-73783203AD2B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B9CF3E8D-4599-40C6-9750-73783203AD2B}.Debug|x64.Build.0 = Debug|Any CPU
{B9CF3E8D-4599-40C6-9750-73783203AD2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9CF3E8D-4599-40C6-9750-73783203AD2B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9CF3E8D-4599-40C6-9750-73783203AD2B}.Release|x64.ActiveCfg = Release|Any CPU
+ {B9CF3E8D-4599-40C6-9750-73783203AD2B}.Release|x64.Build.0 = Release|Any CPU
{1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Debug|x64.Build.0 = Debug|Any CPU
{1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Release|x64.ActiveCfg = Release|Any CPU
+ {1C806CC4-1BC0-4AD9-90A5-B7E9BBB462DD}.Release|x64.Build.0 = Release|Any CPU
{98D7A38A-C3D8-4047-9172-674317FE78CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98D7A38A-C3D8-4047-9172-674317FE78CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98D7A38A-C3D8-4047-9172-674317FE78CF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {98D7A38A-C3D8-4047-9172-674317FE78CF}.Debug|x64.Build.0 = Debug|Any CPU
{98D7A38A-C3D8-4047-9172-674317FE78CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98D7A38A-C3D8-4047-9172-674317FE78CF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98D7A38A-C3D8-4047-9172-674317FE78CF}.Release|x64.ActiveCfg = Release|Any CPU
+ {98D7A38A-C3D8-4047-9172-674317FE78CF}.Release|x64.Build.0 = Release|Any CPU
{949ED95B-18C1-43B3-984D-9144D967C57B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{949ED95B-18C1-43B3-984D-9144D967C57B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {949ED95B-18C1-43B3-984D-9144D967C57B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {949ED95B-18C1-43B3-984D-9144D967C57B}.Debug|x64.Build.0 = Debug|Any CPU
{949ED95B-18C1-43B3-984D-9144D967C57B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{949ED95B-18C1-43B3-984D-9144D967C57B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {949ED95B-18C1-43B3-984D-9144D967C57B}.Release|x64.ActiveCfg = Release|Any CPU
+ {949ED95B-18C1-43B3-984D-9144D967C57B}.Release|x64.Build.0 = Release|Any CPU
{30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Debug|x64.Build.0 = Debug|Any CPU
{30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Release|x64.ActiveCfg = Release|Any CPU
+ {30DFA2A5-1CCB-4AF2-B49C-78D3774260E2}.Release|x64.Build.0 = Release|Any CPU
{ABF54AFD-B968-4600-A0E1-5364021E78D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABF54AFD-B968-4600-A0E1-5364021E78D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABF54AFD-B968-4600-A0E1-5364021E78D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ABF54AFD-B968-4600-A0E1-5364021E78D4}.Debug|x64.Build.0 = Debug|Any CPU
{ABF54AFD-B968-4600-A0E1-5364021E78D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABF54AFD-B968-4600-A0E1-5364021E78D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABF54AFD-B968-4600-A0E1-5364021E78D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {ABF54AFD-B968-4600-A0E1-5364021E78D4}.Release|x64.Build.0 = Release|Any CPU
{E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Debug|x64.Build.0 = Debug|Any CPU
{E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Release|x64.ActiveCfg = Release|Any CPU
+ {E703C033-82F7-4294-B0CE-ACF9703FE0B6}.Release|x64.Build.0 = Release|Any CPU
{84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Debug|x64.Build.0 = Debug|Any CPU
{84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Release|x64.ActiveCfg = Release|Any CPU
+ {84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9}.Release|x64.Build.0 = Release|Any CPU
{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Debug|x64.Build.0 = Debug|Any CPU
{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Release|Any CPU.Build.0 = Release|Any CPU
- {ADB878D3-01EC-4FFD-94D9-4348E1689605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {ADB878D3-01EC-4FFD-94D9-4348E1689605}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {ADB878D3-01EC-4FFD-94D9-4348E1689605}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {ADB878D3-01EC-4FFD-94D9-4348E1689605}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Release|x64.ActiveCfg = Release|Any CPU
+ {D214D1D6-7DBA-4F53-B070-9672BA3F7CBA}.Release|x64.Build.0 = Release|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Debug|x64.Build.0 = Debug|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Release|x64.ActiveCfg = Release|Any CPU
+ {ADEDB772-6FFB-4198-9107-1AD791899679}.Release|x64.Build.0 = Release|Any CPU
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Debug|Any CPU.Build.0 = Debug|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Debug|x64.ActiveCfg = Debug|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Debug|x64.Build.0 = Debug|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Release|Any CPU.ActiveCfg = Release|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Release|x64.ActiveCfg = Release|x64
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964}.Release|x64.Build.0 = Release|x64
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Debug|x64.Build.0 = Debug|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Release|x64.ActiveCfg = Release|Any CPU
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -141,7 +235,9 @@ Global
{E703C033-82F7-4294-B0CE-ACF9703FE0B6} = {677ECDC0-9125-4E30-8B8C-E0CC5F98DFF5}
{84D6A2DD-327D-4CF4-AC94-8DF9A910E7A9} = {677ECDC0-9125-4E30-8B8C-E0CC5F98DFF5}
{D214D1D6-7DBA-4F53-B070-9672BA3F7CBA} = {12AAC0C3-970D-43F6-BD47-0B662B7157F6}
- {ADB878D3-01EC-4FFD-94D9-4348E1689605} = {677ECDC0-9125-4E30-8B8C-E0CC5F98DFF5}
+ {ADEDB772-6FFB-4198-9107-1AD791899679} = {677ECDC0-9125-4E30-8B8C-E0CC5F98DFF5}
+ {B3675F7D-DF3F-4F0E-A9E0-9575381BC964} = {E876F453-952B-4D58-AA0E-1D95DFF58FB5}
+ {0D0785D7-4A07-4FA0-919F-FD3AB6AED2F4} = {75B25179-1B35-461D-80AA-80EF52E7E101}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F04B3FFB-34D2-4BBE-85BA-F28DEE1BCAB6}
diff --git a/Application/Models/Config/Config.csproj b/Application/Models/Config/Config.csproj
index f26cea2..407cd23 100644
--- a/Application/Models/Config/Config.csproj
+++ b/Application/Models/Config/Config.csproj
@@ -4,6 +4,7 @@
+
diff --git a/Application/Models/Config/Configs/ProjectConfig.cs b/Application/Models/Config/Configs/ProjectConfig.cs
index 969351b..16f8850 100644
--- a/Application/Models/Config/Configs/ProjectConfig.cs
+++ b/Application/Models/Config/Configs/ProjectConfig.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using BocchiTracker.Config.Parts;
using BocchiTracker.ServiceClientData;
namespace BocchiTracker.Config.Configs
@@ -42,14 +43,11 @@ public class ServiceConfig
public List DefaultValue { get; set;} = new List();
}
- public class ExternalToolsPath
- {
- public string? ProcDumpPath { get; set; }
- }
-
public class ProjectConfig
{
- public int Port { get; set; } = 8888;
+ public int Port { get; set; } = 8888;
+
+ public int WebSocketPort { get; set; } = 8822;
public List TicketTypes { get; set; } = new List { "Bug", "Task", "Question" };
diff --git a/Application/Models/Config/Configs/UserConfig.cs b/Application/Models/Config/Configs/UserConfig.cs
index c068401..a0cdc11 100644
--- a/Application/Models/Config/Configs/UserConfig.cs
+++ b/Application/Models/Config/Configs/UserConfig.cs
@@ -1,4 +1,5 @@
-using BocchiTracker.ServiceClientData;
+using BocchiTracker.Config.Parts;
+using BocchiTracker.ServiceClientData;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,6 +10,8 @@ namespace BocchiTracker.Config.Configs
{
public class UserConfig
{
+ public CaptureSetting CaptureSetting { get; set; } = new CaptureSetting();
+
public string? ProjectConfigFilename { get; set; }
public bool IsOpenWebBrowser { get; set; }
diff --git a/Application/Models/Config/GameCaptureType.cs b/Application/Models/Config/GameCaptureType.cs
new file mode 100644
index 0000000..b47598c
--- /dev/null
+++ b/Application/Models/Config/GameCaptureType.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.Config
+{
+ public enum GameCaptureType
+ {
+ OBSStudio,
+ WebRTC,
+
+ NotUse,
+ }
+}
diff --git a/Application/Models/Config/Parts/CaptureSetting.cs b/Application/Models/Config/Parts/CaptureSetting.cs
new file mode 100644
index 0000000..f380dcd
--- /dev/null
+++ b/Application/Models/Config/Parts/CaptureSetting.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.Config.Parts
+{
+ public class CaptureSetting
+ {
+ public GameCaptureType GameCaptureType { get; set; } = GameCaptureType.NotUse;
+
+ public SIPSorceryMedia.Abstractions.VideoCodecsEnum VideoCodecs { get; set; } = SIPSorceryMedia.Abstractions.VideoCodecsEnum.VP8;
+
+ public bool IncludeAudio = false;
+
+ public int RecordingFrameRate { get; set; } = 30;
+
+ public int RecordingMintes { get; set; } = 3;
+ }
+}
diff --git a/Application/Models/Config/Parts/ExternalToolsPath.cs b/Application/Models/Config/Parts/ExternalToolsPath.cs
new file mode 100644
index 0000000..af1815c
--- /dev/null
+++ b/Application/Models/Config/Parts/ExternalToolsPath.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.Config.Parts
+{
+ public class ExternalToolsPath
+ {
+ public string? ProcDumpPath { get; set; }
+
+ public string? FFmpegPath { get; set; }
+ }
+}
diff --git a/Application/Models/GameCaptureRTC/CaptureFrameStorage.cs b/Application/Models/GameCaptureRTC/CaptureFrameStorage.cs
new file mode 100644
index 0000000..5dc2883
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/CaptureFrameStorage.cs
@@ -0,0 +1,155 @@
+
+using BocchiTracker.ModelEvent;
+using FFMpegCore;
+using OpenCvSharp;
+using Prism.Events;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.GameCaptureRTC
+{
+ public class CaptureFrameStorage : IDisposable
+ {
+ private object _mutext = new object();
+
+ private int _maxRecordingFrameCount = 0;
+ private int _maxSplitFrameCount = 0;
+ private int _curFrameCount = 0;
+ private int _curSpliteFrameCount = 0;
+
+ private string _tempCancatMovieDirectory = Path.Combine(Path.GetTempPath(), "BocchiTracker", "temp", "concat_movies");
+ private string _tempMovieDirectory = Path.Combine(Path.GetTempPath(), "BocchiTracker", "temp", "movies");
+ private string _tempPicsDirectory = Path.Combine(Path.GetTempPath(), "BocchiTracker", "temp", "pics");
+
+ private int _movieID = 0;
+ private int _adjustedwidth = 640;
+ private int _adjustedHeight = 480;
+
+ private VideoWriter _videoWriter = default!;
+
+ public CaptureFrameStorage(string inFFmpegPath, int inMaxRecordingFrameCount, int inMaxSplitFrameCount)
+ {
+ GlobalFFOptions.Configure(options => options.BinaryFolder = inFFmpegPath);
+
+ _maxSplitFrameCount = inMaxSplitFrameCount;
+ _maxRecordingFrameCount = inMaxRecordingFrameCount;
+
+ if (!Directory.Exists(_tempMovieDirectory))
+ Directory.CreateDirectory(_tempMovieDirectory);
+ if (!Directory.Exists(_tempCancatMovieDirectory))
+ Directory.CreateDirectory(_tempCancatMovieDirectory);
+ if (!Directory.Exists(_tempPicsDirectory))
+ Directory.CreateDirectory(_tempPicsDirectory);
+
+ Cleanup();
+ }
+
+ public void AddFrame(int inWidth, int inHeight, int inStride, nint inData)
+ {
+ lock(_mutext)
+ {
+ if(_adjustedwidth > inWidth || _adjustedwidth == 0)
+ _adjustedwidth = inWidth % 2 == 0 ? inWidth : inWidth - 1;
+ if (_adjustedHeight > inHeight || _adjustedHeight == 0)
+ _adjustedHeight = inHeight % 2 == 0 ? inHeight : inHeight - 1;
+
+ if (_curSpliteFrameCount == 0)
+ {
+ System.Console.WriteLine("start video capture");
+ if (_videoWriter == null || _videoWriter.IsDisposed)
+ _videoWriter = new VideoWriter();
+ _videoWriter.Open(Path.Combine(_tempMovieDirectory, $"movie.{_movieID}.mp4"), FourCC.MPG4, 30, new OpenCvSharp.Size(inWidth, inHeight));
+ ++_movieID;
+ }
+
+ unsafe
+ {
+ byte[] dataCopy = new byte[inHeight * inStride];
+ unsafe
+ {
+ byte* src = (byte*)inData;
+ for (int i = 0; i < dataCopy.Length; i++)
+ {
+ dataCopy[i] = *(src + i);
+ }
+ }
+
+ using (var mat = new Mat(inHeight, inWidth, MatType.CV_8UC3, dataCopy, inStride))
+ {
+ _videoWriter.Write(mat);
+
+ _curFrameCount++;
+ _curSpliteFrameCount++;
+ }
+ }
+
+ if (_curSpliteFrameCount > _maxSplitFrameCount)
+ {
+ System.Console.WriteLine("_videoWriter wrote maximum frame, so next video...");
+ _curSpliteFrameCount = 0;
+ _videoWriter.Dispose();
+ }
+
+ if (_curFrameCount > _maxRecordingFrameCount)
+ {
+ var bochi_files = Directory.GetFiles(_tempMovieDirectory, "*.mp4")
+ .OrderBy(file => int.Parse(Regex.Match(file, @"(?<=movie\.)(\d+)(?=\.mp4)").Value))
+ .ToList();
+ if(bochi_files.Any())
+ {
+ System.Console.WriteLine("the maximum recording time has been exceeded, so remove old video file");
+ File.Delete(bochi_files[0]);
+ }
+ _curFrameCount -= _maxSplitFrameCount;
+ }
+ }
+ }
+
+ public string ConcatMovie()
+ {
+ lock (_mutext)
+ {
+ Dispose();
+ _curFrameCount = 0;
+ _curSpliteFrameCount = 0;
+
+ var output = Path.Combine(_tempCancatMovieDirectory, "bocchi_movie.mp4");
+ var bochi_files = Directory.GetFiles(_tempMovieDirectory, "*.mp4");
+ if (!bochi_files.Any())
+ return string.Empty;
+
+ var command = FFMpegArguments
+ .FromDemuxConcatInput(bochi_files)
+ .OutputToFile(output, overwrite: true, op => op.Resize(_adjustedwidth, _adjustedHeight));
+ bool ret = command.ProcessSynchronously();
+ if (ret)
+ Cleanup();
+ return output;
+ }
+ }
+
+ public void Cleanup()
+ {
+ var bochi_files = Directory.GetFiles(_tempMovieDirectory, "*.mp4");
+ foreach (var file in bochi_files)
+ {
+ File.Delete(file);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_videoWriter != null && !_videoWriter.IsDisposed)
+ {
+ _videoWriter.Dispose();
+ }
+ }
+ }
+}
diff --git a/Application/Models/GameCaptureRTC/GameCaptureRTC.csproj b/Application/Models/GameCaptureRTC/GameCaptureRTC.csproj
new file mode 100644
index 0000000..090d987
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/GameCaptureRTC.csproj
@@ -0,0 +1,37 @@
+
+
+
+
+
+ True
+ net7.0-windows7.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Application/Models/GameCaptureRTC/Module.cs b/Application/Models/GameCaptureRTC/Module.cs
new file mode 100644
index 0000000..6775285
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/Module.cs
@@ -0,0 +1,16 @@
+using Prism.Ioc;
+using Prism.Modularity;
+
+namespace BocchiTracker.GameCaptureRTC
+{
+ public class GameCaptureRTCModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider) {}
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterSingleton(typeof(CaptureFrameStorage));
+ containerRegistry.RegisterSingleton(typeof(RecordingController));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Application/Models/GameCaptureRTC/Protocol/ICaptureProtocol.cs b/Application/Models/GameCaptureRTC/Protocol/ICaptureProtocol.cs
new file mode 100644
index 0000000..35a1554
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/Protocol/ICaptureProtocol.cs
@@ -0,0 +1,18 @@
+using BocchiTracker.Config.Configs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.GameCaptureRTC.Protocol
+{
+ public interface ICaptureProtocol
+ {
+ void Start(int inPort, string inFFmpegPath, Config.Parts.CaptureSetting inCaptureSetting);
+
+ void Stop();
+
+ bool IsConnect();
+ }
+}
diff --git a/Application/Models/GameCaptureRTC/Protocol/OBSCapture.cs b/Application/Models/GameCaptureRTC/Protocol/OBSCapture.cs
new file mode 100644
index 0000000..86119d3
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/Protocol/OBSCapture.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BocchiTracker.Config.Configs;
+using OBSWebsocketDotNet;
+
+namespace BocchiTracker.GameCaptureRTC.Protocol
+{
+ public class OBSCapture : ICaptureProtocol
+ {
+ private OBSWebsocket _obsWebSocket = default!;
+
+ public OBSCapture() {}
+
+ public bool IsConnect()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Start(int inPort, string inFFmpegPath, Config.Parts.CaptureSetting inCaptureSetting)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Stop()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Application/Models/GameCaptureRTC/Protocol/WebRTC.cs b/Application/Models/GameCaptureRTC/Protocol/WebRTC.cs
new file mode 100644
index 0000000..977f927
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/Protocol/WebRTC.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using WebSocketSharp.Server;
+using Prism.Events;
+using BocchiTracker.ModelEvent;
+using SIPSorcery.Net;
+using SIPSorceryMedia.Abstractions;
+using SIPSorceryMedia.FFmpeg;
+using BocchiTracker.Config.Configs;
+using WebSocketSharp;
+using Newtonsoft;
+using Newtonsoft.Json.Linq;
+using System.Net.Sockets;
+using System.Threading;
+using System.Reflection.Metadata;
+using System.IO;
+
+namespace BocchiTracker.GameCaptureRTC.Protocol
+{
+ public class WebRTCWebSocketPeer : WebSocketBehavior
+ {
+ private RTCPeerConnection _pc = default!;
+
+ public Func> CreatePeerConnection = default!;
+
+ public RTCPeerConnection RTCPeerConnection => _pc;
+
+ public RTCOfferOptions OfferOptions { get; set; } = default!;
+
+ public RTCAnswerOptions AnswerOptions { get; set; } = default!;
+
+ protected override async void OnMessage(MessageEventArgs e)
+ {
+ RTCSessionDescriptionInit init2;
+ if (RTCIceCandidateInit.TryParse(e.Data, out var init))
+ {
+ Console.WriteLine("Got remote ICE candidate.");
+ _pc.addIceCandidate(init);
+ }
+ else if (RTCSessionDescriptionInit.TryParse(e.Data, out init2))
+ {
+ Console.WriteLine($"Got remote SDP, type {init2.type}.");
+ SetDescriptionResultEnum setDescriptionResultEnum = _pc.setRemoteDescription(init2);
+ if (setDescriptionResultEnum != 0)
+ {
+ Console.WriteLine($"Failed to set remote description, {setDescriptionResultEnum}.");
+ _pc.Close("failed to set remote description");
+ base.Close();
+ }
+ else if (_pc.signalingState == RTCSignalingState.have_remote_offer)
+ {
+ RTCSessionDescriptionInit answerSdp = _pc.createAnswer(AnswerOptions);
+ await _pc.setLocalDescription(answerSdp).ConfigureAwait(continueOnCapturedContext: false);
+ Console.WriteLine($"Sending SDP answer to client {Context.UserEndPoint}.");
+ Context.WebSocket.Send(AddPlayerIdJson(answerSdp.toJSON()));
+ }
+ }
+ }
+
+ protected override async void OnOpen()
+ {
+ base.OnOpen();
+ Console.WriteLine($"Web socket client connection from {Context.UserEndPoint}.");
+ _pc = await CreatePeerConnection().ConfigureAwait(continueOnCapturedContext: false);
+
+ var rtc_config = _pc.getConfiguration();
+ var data = Newtonsoft.Json.JsonConvert.SerializeObject(
+ new Dictionary
+ {
+ { "type", "config" },
+ { "peerConnectionOptions", new Dictionary {
+ { "bundlePolicy", rtc_config.bundlePolicy.ToString() },
+ { "certificates", rtc_config.certificates },
+ { "iceCandidatePoolSize", rtc_config.iceCandidatePoolSize },
+ { "iceServers", rtc_config.iceServers },
+ { "iceTransportPolicy", rtc_config.iceTransportPolicy.ToString() },
+ { "rtcpMuxPolicy", rtc_config.rtcpMuxPolicy.ToString() },
+ }
+ },
+ }
+ );
+ Context.WebSocket.Send(data);
+
+ _pc.onicecandidate += delegate (RTCIceCandidate iceCandidate)
+ {
+ if (_pc.signalingState == RTCSignalingState.have_remote_offer || _pc.signalingState == RTCSignalingState.stable)
+ {
+ Context.WebSocket.Send(AddPlayerIdJson(iceCandidate.toJSON()));
+ }
+ };
+
+ RTCSessionDescriptionInit offerSdp = _pc.createOffer(OfferOptions);
+ await _pc.setLocalDescription(offerSdp).ConfigureAwait(continueOnCapturedContext: false);
+ Console.WriteLine($"Sending SDP offer to client {Context.UserEndPoint}.");
+ Context.WebSocket.Send(AddPlayerIdJson(offerSdp.toJSON()));
+ }
+
+ private string AddPlayerIdJson(string inJson)
+ {
+ string custom_json = inJson;
+ custom_json = custom_json.Insert(inJson.LastIndexOf('}'), ", \"playerid\": \"BocchiTrackerPlayer\"");
+ return custom_json;
+ }
+ }
+
+ public class WebRTC : WebSocketBehavior, ICaptureProtocol, IDisposable
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private WebSocketServer _web_socket = default!;
+ private CaptureFrameStorage _captureFrameStorage = default!;
+ private static bool _isConnecting;
+ private SubscriptionToken _subscriptionToken;
+
+ public WebRTC(IEventAggregator inEventAggregator)
+ {
+ _eventAggregator = inEventAggregator;
+ _subscriptionToken = _eventAggregator.GetEvent().Subscribe(Stop, ThreadOption.BackgroundThread);
+ }
+
+ public void Dispose()
+ {
+ _web_socket.Stop();
+ _eventAggregator.GetEvent().Unsubscribe(_subscriptionToken);
+ }
+
+ public bool IsConnect()
+ {
+ return _isConnecting;
+ }
+
+ public void Start(int inPort, string inFFmpegPath, Config.Parts.CaptureSetting inCaptureSetting)
+ {
+ string? ffmpeg = inFFmpegPath;
+ if (ffmpeg == null || !Path.Exists(ffmpeg))
+ throw new Exception("FFmpeg path is not set.");
+
+ int maxFrameCount = (60 * inCaptureSetting.RecordingFrameRate) * inCaptureSetting.RecordingMintes;
+ _captureFrameStorage = new CaptureFrameStorage(ffmpeg, maxFrameCount, maxFrameCount / 10);
+ _web_socket = new WebSocketServer(IPAddress.Any, inPort, false);
+ _web_socket.Log.Level = WebSocketSharp.LogLevel.Trace;
+ _web_socket.AllowForwardedRequest = true;
+ _web_socket.AddWebSocketService("/", (peer) => peer.CreatePeerConnection = () => CreatePeerConnection(ffmpeg, inCaptureSetting, _captureFrameStorage));
+ _web_socket.Start();
+ }
+
+ public void Stop()
+ {
+ string movie = _captureFrameStorage.ConcatMovie();
+ if(movie.IsNullOrEmpty())
+ return;
+ _eventAggregator.GetEvent().Publish(movie);
+ }
+
+ private static Task CreatePeerConnection(string inFFmpegPath, Config.Parts.CaptureSetting inCaptureSetting, CaptureFrameStorage inFrameStorage)
+ {
+ FFmpegInit.Initialise(FfmpegLogLevelEnum.AV_LOG_VERBOSE, inFFmpegPath);
+ var videoEP = new FFmpegVideoEndPoint();
+ videoEP.RestrictFormats(format => format.Codec == inCaptureSetting.VideoCodecs);
+
+ videoEP.OnVideoSinkDecodedSampleFaster += (RawImage rawImage) =>
+ {
+ if (rawImage.PixelFormat == SIPSorceryMedia.Abstractions.VideoPixelFormatsEnum.Rgb)
+ {
+ inFrameStorage.AddFrame((int)rawImage.Width, (int)rawImage.Height, (int)rawImage.Stride, rawImage.Sample);
+ }
+ };
+
+ RTCConfiguration config = new RTCConfiguration
+ {
+ //iceServers = new List { new RTCIceServer { urls = STUN_URL } }
+ X_UseRtpFeedbackProfile = true
+ };
+ var pc = new RTCPeerConnection(config);
+
+ if (inCaptureSetting.IncludeAudio)
+ {
+ MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false,
+ new List { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.RecvOnly);
+ pc.addTrack(audioTrack);
+ }
+ MediaStreamTrack videoTrack = new MediaStreamTrack(videoEP.GetVideoSinkFormats(), MediaStreamStatusEnum.RecvOnly);
+ pc.addTrack(videoTrack);
+
+ pc.OnVideoFrameReceived += videoEP.GotVideoFrame;
+ pc.OnVideoFormatsNegotiated += (formats) => videoEP.SetVideoSinkFormat(formats.First());
+ pc.onconnectionstatechange += async (state) =>
+ {
+ Console.WriteLine($"Peer connection state change to {state}.");
+ switch (state)
+ {
+ case RTCPeerConnectionState.failed: pc.Close("ice disconnection"); break;
+ case RTCPeerConnectionState.closed: await videoEP.CloseVideo(); break;
+ default: break;
+ }
+ };
+ pc.OnSendReport += (media, sr) => Console.WriteLine($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
+ pc.oniceconnectionstatechange += (state) =>
+ {
+ Console.WriteLine($"ICE connection state change to {state}.");
+ switch (state)
+ {
+ case RTCIceConnectionState.connected: _isConnecting = true; break;
+ default: _isConnecting = false; break;
+ }
+ };
+ return Task.FromResult(pc);
+ }
+ }
+}
diff --git a/Application/Models/GameCaptureRTC/RecordingController.cs b/Application/Models/GameCaptureRTC/RecordingController.cs
new file mode 100644
index 0000000..90fa955
--- /dev/null
+++ b/Application/Models/GameCaptureRTC/RecordingController.cs
@@ -0,0 +1,54 @@
+using BocchiTracker.Config.Configs;
+using BocchiTracker.GameCaptureRTC.Protocol;
+using BocchiTracker.ModelEvent;
+using Prism.Events;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.GameCaptureRTC
+{
+ public class RecordingController : ICaptureProtocol
+ {
+ private readonly IEventAggregator _eventAggregator;
+
+ private ICaptureProtocol? _captureProtocol;
+
+ public RecordingController(IEventAggregator inEventAggregator)
+ {
+ _eventAggregator = inEventAggregator;
+ }
+
+ public bool IsConnect()
+ {
+ if (_captureProtocol == null)
+ return false;
+ return _captureProtocol.IsConnect();
+ }
+
+ public void Start(int inPort, string inFFmpegPath, Config.Parts.CaptureSetting inCaptureSetting)
+ {
+ switch (inCaptureSetting.GameCaptureType)
+ {
+ case Config.GameCaptureType.OBSStudio:
+ {
+ _captureProtocol = new Protocol.OBSCapture();
+ }
+ break;
+ case Config.GameCaptureType.WebRTC:
+ {
+ _captureProtocol = new Protocol.WebRTC(_eventAggregator);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if(_captureProtocol != null)
+ _captureProtocol.Start(inPort, inFFmpegPath, inCaptureSetting);
+ }
+
+ public void Stop()
+ {
+ _captureProtocol?.Stop();
+ }
+ }
+}
diff --git a/Application/Models/ImageProcessorAsync/ImageProcessorAsync.csproj b/Application/Models/ImageProcessorAsync/ImageProcessorAsync.csproj
deleted file mode 100644
index b94fd81..0000000
--- a/Application/Models/ImageProcessorAsync/ImageProcessorAsync.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- net7.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Application/Models/IssueAssetCollector/AssetData.cs b/Application/Models/IssueAssetCollector/AssetData.cs
index 047d683..df9b7cb 100644
--- a/Application/Models/IssueAssetCollector/AssetData.cs
+++ b/Application/Models/IssueAssetCollector/AssetData.cs
@@ -7,7 +7,6 @@
using SixLabors.ImageSharp.Formats;
using System.Threading.Tasks;
using System.ComponentModel;
-using BocchiTracker.ImageProcessorAsync;
using System.Reflection;
using System;
using static System.Net.Mime.MediaTypeNames;
diff --git a/Application/Models/IssueAssetCollector/Handlers/CreateActionHandler.cs b/Application/Models/IssueAssetCollector/Handlers/CreateActionHandler.cs
index bda6986..c956392 100644
--- a/Application/Models/IssueAssetCollector/Handlers/CreateActionHandler.cs
+++ b/Application/Models/IssueAssetCollector/Handlers/CreateActionHandler.cs
@@ -2,6 +2,7 @@
using BocchiTracker.Config.Configs;
using BocchiTracker.IssueAssetCollector.Handlers.Coredump;
using BocchiTracker.IssueAssetCollector.Handlers.Log;
+using BocchiTracker.IssueAssetCollector.Handlers.Movie;
using BocchiTracker.IssueAssetCollector.Handlers.Screenshot;
using BocchiTracker.ModelEvent;
using BocchiTracker.ProcessLinkQuery.Queries;
@@ -26,13 +27,15 @@ public class CreateActionHandler : ICreateActionHandler
private readonly IEventAggregator _eventAggregator;
private readonly IFilenameGeneratorFactory _filenameGeneratorFactory;
private readonly ProjectConfig _projectConfig;
+ private readonly UserConfig _userConfig;
private readonly AppStatusBundles _appStatusBundles;
- public CreateActionHandler(IEventAggregator inEventAggregator, IFilenameGeneratorFactory inFilenameGenFac, AppStatusBundles inAppStatusBundles, ProjectConfig inConfig)
+ public CreateActionHandler(IEventAggregator inEventAggregator, IFilenameGeneratorFactory inFilenameGenFac, AppStatusBundles inAppStatusBundles, ProjectConfig inProjectConfig, UserConfig inUserConfig)
{
_eventAggregator = inEventAggregator;
_filenameGeneratorFactory = inFilenameGenFac;
- _projectConfig = inConfig;
+ _projectConfig = inProjectConfig;
+ _userConfig = inUserConfig;
_appStatusBundles = inAppStatusBundles;
}
@@ -71,6 +74,12 @@ public IHandle Create(Type inType)
_cacheHandles.Add(inType, handler);
}
+ if(inType == typeof(MovieHandler))
+ {
+ var handler = new MovieHandler(_eventAggregator, _filenameGeneratorFactory.GetFilenameGenerator(typeof(TimestampedFilenameGenerator)));
+ _cacheHandles.Add(inType, handler);
+ }
+
return _cacheHandles[inType];
}
}
diff --git a/Application/Models/IssueAssetCollector/Handlers/Movie/MovieHandler.cs b/Application/Models/IssueAssetCollector/Handlers/Movie/MovieHandler.cs
new file mode 100644
index 0000000..7a41cff
--- /dev/null
+++ b/Application/Models/IssueAssetCollector/Handlers/Movie/MovieHandler.cs
@@ -0,0 +1,52 @@
+using BocchiTracker.ApplicationInfoCollector;
+using BocchiTracker.ModelEvent;
+using Prism.Events;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.IssueAssetCollector.Handlers.Movie
+{
+ public class GameCaptureFrameConvertMovieProcess
+ {
+ public string Output { get; set; } = string.Empty;
+ private IEventAggregator _eventAggregator;
+
+ public GameCaptureFrameConvertMovieProcess(IEventAggregator inEventAggregator)
+ {
+ _eventAggregator = inEventAggregator;
+ _eventAggregator
+ .GetEvent()
+ .Subscribe(OnGameCaptureFinishEvent, ThreadOption.BackgroundThread);
+ }
+
+ public void OnGameCaptureFinishEvent(string inMoviePath)
+ {
+ File.Copy(inMoviePath, Output, true);
+ }
+ }
+
+ public class MovieHandler : IHandle
+ {
+ public IFilenameGenerator _filenameGenerator { private set; get; }
+ private IEventAggregator _eventAggregator;
+ private GameCaptureFrameConvertMovieProcess _convert_movie_process;
+
+ public MovieHandler(IEventAggregator inEventAggregator, IFilenameGenerator inFilenameGenerator)
+ {
+ _filenameGenerator = inFilenameGenerator;
+ _eventAggregator = inEventAggregator;
+ _convert_movie_process = new GameCaptureFrameConvertMovieProcess(inEventAggregator);
+ }
+
+ public void Handle(AppStatusBundle inAppStatusBundle, int inPID, string inOutput)
+ {
+ this._convert_movie_process.Output = Path.Combine(inOutput, _filenameGenerator.Generate(inAppStatusBundle) + ".mp4");
+ _eventAggregator.GetEvent().Publish();
+ }
+ }
+}
diff --git a/Application/Models/IssueAssetCollector/Handlers/Screenshot/RemoteScreenshotHandler.cs b/Application/Models/IssueAssetCollector/Handlers/Screenshot/RemoteScreenshotHandler.cs
index 8758fc5..b0bd372 100644
--- a/Application/Models/IssueAssetCollector/Handlers/Screenshot/RemoteScreenshotHandler.cs
+++ b/Application/Models/IssueAssetCollector/Handlers/Screenshot/RemoteScreenshotHandler.cs
@@ -1,5 +1,4 @@
using BocchiTracker.ApplicationInfoCollector;
-using BocchiTracker.ImageProcessorAsync;
using BocchiTracker.ModelEvent;
using BocchiTracker.ProcessLinkQuery.Queries;
using Prism.Events;
diff --git a/Application/Models/ImageProcessorAsync/ImageProcessor.cs b/Application/Models/IssueAssetCollector/ImageProcessor.cs
similarity index 95%
rename from Application/Models/ImageProcessorAsync/ImageProcessor.cs
rename to Application/Models/IssueAssetCollector/ImageProcessor.cs
index 543c17d..5493d7f 100644
--- a/Application/Models/ImageProcessorAsync/ImageProcessor.cs
+++ b/Application/Models/IssueAssetCollector/ImageProcessor.cs
@@ -4,9 +4,11 @@
using SixLabors.ImageSharp;
using System.IO;
using System.Reflection;
+using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
+using System.Threading;
-namespace BocchiTracker.ImageProcessorAsync
+namespace BocchiTracker.IssueAssetCollector
{
public class ImageProcessor
{
diff --git a/Application/Models/IssueAssetCollector/IssueAssetCollector.csproj b/Application/Models/IssueAssetCollector/IssueAssetCollector.csproj
index 1c30d86..fb9b6fc 100644
--- a/Application/Models/IssueAssetCollector/IssueAssetCollector.csproj
+++ b/Application/Models/IssueAssetCollector/IssueAssetCollector.csproj
@@ -2,6 +2,10 @@
+
+ True
+
+
@@ -13,12 +17,12 @@
+
-
diff --git a/Application/Models/ModelEvent/GameCaptureEvent.cs b/Application/Models/ModelEvent/GameCaptureEvent.cs
new file mode 100644
index 0000000..791990e
--- /dev/null
+++ b/Application/Models/ModelEvent/GameCaptureEvent.cs
@@ -0,0 +1,15 @@
+using Prism.Events;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BocchiTracker.ModelEvent
+{
+ public class GameCaptureStopRequest : PubSubEvent { }
+
+ public class GameCaptureFinishEvent : PubSubEvent { }
+}
diff --git a/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker - Backup.WebRTCTest.csproj b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker - Backup.WebRTCTest.csproj
new file mode 100644
index 0000000..f6d0f85
--- /dev/null
+++ b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker - Backup.WebRTCTest.csproj
@@ -0,0 +1,19 @@
+
+
+
+ true
+ WinExe
+ disable
+ net7.0-windows
+ False
+ YutoArita
+ ..\Artifact
+
+
+
+
+
+
+
+
+
diff --git a/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj
new file mode 100644
index 0000000..4a1f520
--- /dev/null
+++ b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj
@@ -0,0 +1,28 @@
+
+
+
+ true
+ Exe
+ disable
+ net7.0-windows7.0
+ False
+ YutoArita
+ ..\Artifact
+ true
+ x64
+ 7.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj.user b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj.user
new file mode 100644
index 0000000..88a5509
--- /dev/null
+++ b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.csproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.sln b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.sln
new file mode 100644
index 0000000..d941244
--- /dev/null
+++ b/Application/Tests/BocchiTracker.WebRTCTest/BocchiTracker.WebRTCTest.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BocchiTracker.WebRTCTest", "BocchiTracker.WebRTCTest.csproj", "{BB1C689F-71B7-4F47-BB65-326816806C84}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Debug|x64.ActiveCfg = Debug|x64
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Debug|x64.Build.0 = Debug|x64
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Release|x64.ActiveCfg = Release|x64
+ {BB1C689F-71B7-4F47-BB65-326816806C84}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {577FD981-9EE6-49A7-A5BD-274536A0FBAC}
+ EndGlobalSection
+EndGlobal
diff --git a/Application/Tests/BocchiTracker.WebRTCTest/Program.cs b/Application/Tests/BocchiTracker.WebRTCTest/Program.cs
new file mode 100644
index 0000000..adf0416
--- /dev/null
+++ b/Application/Tests/BocchiTracker.WebRTCTest/Program.cs
@@ -0,0 +1,42 @@
+using BocchiTracker.GameCaptureRTC;
+using BocchiTracker.IssueAssetCollector.Handlers.Movie;
+using BocchiTracker.ModelEvent;
+using Prism.Events;
+using System;
+using System.Threading;
+
+namespace BocchiTracker.WebRTCTest
+{
+ class Program
+ {
+ static void Main()
+ {
+ string ffmpeg = "put your ffmpeg path";
+
+ var eventAggregator = new EventAggregator();
+ var recordingController = new RecordingController(eventAggregator);
+ var movieSaveProcess = new GameCaptureFrameConvertMovieProcess(eventAggregator);
+
+ var p_config = new Config.Configs.ProjectConfig();
+ var u_config = new Config.Configs.UserConfig
+ {
+ CaptureSetting = new Config.Parts.CaptureSetting
+ {
+ VideoCodecs = SIPSorceryMedia.Abstractions.VideoCodecsEnum.VP8
+ }
+ };
+
+ Console.WriteLine("サーバー接続中...");
+ while (!recordingController.IsConnect())
+ Thread.Sleep(10);
+
+ Console.WriteLine("キャプチャーを開始しました。");
+ {
+ recordingController.Start(p_config.WebSocketPort, ffmpeg, u_config.CaptureSetting);
+ Thread.Sleep(5000);
+ recordingController.Stop();
+ }
+ Console.WriteLine("キャプチャーを停止しました。");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Application/WPF/BocchiTracker.Client.Config/ViewModels/DirectoryViewModel.cs b/Application/WPF/BocchiTracker.Client.Config/ViewModels/DirectoryViewModel.cs
index b793a54..f9f0e16 100644
--- a/Application/WPF/BocchiTracker.Client.Config/ViewModels/DirectoryViewModel.cs
+++ b/Application/WPF/BocchiTracker.Client.Config/ViewModels/DirectoryViewModel.cs
@@ -72,10 +72,15 @@ public class ExternalToolPathes
{
public ReactiveProperty ProcdumpPath { get; set; }
+ public ReactiveProperty FFmpegPath { get; set; }
+
public ExternalToolPathes(ProjectConfig inProjectConfig)
{
ProcdumpPath = new ReactiveProperty();
ProcdumpPath.Subscribe(value => inProjectConfig.ExternalToolsPath.ProcDumpPath = value);
+
+ FFmpegPath = new ReactiveProperty();
+ FFmpegPath.Subscribe(value => inProjectConfig.ExternalToolsPath.FFmpegPath = value);
}
}
@@ -124,6 +129,7 @@ private void OnConfigReload(ConfigReloadEventParameter inParam)
MonitoredDirectories.OnAddItem(new Tuple(dir.Directory, dir.Filter));
}
ExternalToolPathes.ProcdumpPath.Value = config.ExternalToolsPath.ProcDumpPath;
+ ExternalToolPathes.FFmpegPath.Value = config.ExternalToolsPath.FFmpegPath;
FileSaveDirectory.WorkingDirectory.Value = config.FileSaveDirectory;
FileSaveDirectory.CacheDirectory.Value = config.CacheDirectory;
}
@@ -144,6 +150,7 @@ private void OnSaveConfig()
projectConfig.MonitoredDirectoryConfigs.Add(moniteredDirectory);
}
projectConfig.ExternalToolsPath.ProcDumpPath = ExternalToolPathes.ProcdumpPath.Value;
+ projectConfig.ExternalToolsPath.FFmpegPath = ExternalToolPathes.FFmpegPath.Value;
projectConfig.CacheDirectory = FileSaveDirectory.CacheDirectory.Value;
projectConfig.FileSaveDirectory = FileSaveDirectory.WorkingDirectory.Value;
}
diff --git a/Application/WPF/BocchiTracker.Client.Config/ViewModels/GeneralViewModel.cs b/Application/WPF/BocchiTracker.Client.Config/ViewModels/GeneralViewModel.cs
index 2c97a3d..4912a54 100644
--- a/Application/WPF/BocchiTracker.Client.Config/ViewModels/GeneralViewModel.cs
+++ b/Application/WPF/BocchiTracker.Client.Config/ViewModels/GeneralViewModel.cs
@@ -62,11 +62,17 @@ public AuthenticationURL Slack
[Range(1024, 65535, ErrorMessage = "Please enter value in 1024~65535")]
public ReactiveProperty TcpPort { get; set; }
+ [Range(1024, 65535, ErrorMessage = "Please enter value in 1024~65535")]
+ public ReactiveProperty WebSocketPort { get; set; }
+
public GeneralViewModel(IEventAggregator inEventAggregator, ProjectConfig inProjectConfig)
{
TcpPort = new ReactiveProperty("8888").SetValidateAttribute(() => this.TcpPort);
TcpPort.Subscribe(value => inProjectConfig.Port = int.Parse(value));
+ WebSocketPort = new ReactiveProperty("8822").SetValidateAttribute(() => this.WebSocketPort);
+ WebSocketPort.Subscribe(value => inProjectConfig.WebSocketPort = int.Parse(value));
+
inEventAggregator
.GetEvent()
.Subscribe(OnConfigReload, ThreadOption.UIThread);
diff --git a/Application/WPF/BocchiTracker.Client.Config/Views/DirectoryView.xaml b/Application/WPF/BocchiTracker.Client.Config/Views/DirectoryView.xaml
index f883f2d..a3cc051 100644
--- a/Application/WPF/BocchiTracker.Client.Config/Views/DirectoryView.xaml
+++ b/Application/WPF/BocchiTracker.Client.Config/Views/DirectoryView.xaml
@@ -30,9 +30,16 @@
-
+
+
+
+
+
diff --git a/Application/WPF/BocchiTracker.Client.Config/Views/GeneralView.xaml b/Application/WPF/BocchiTracker.Client.Config/Views/GeneralView.xaml
index 59f5dca..85dc492 100644
--- a/Application/WPF/BocchiTracker.Client.Config/Views/GeneralView.xaml
+++ b/Application/WPF/BocchiTracker.Client.Config/Views/GeneralView.xaml
@@ -41,20 +41,36 @@
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Application/WPF/BocchiTracker.Client/App.xaml.cs b/Application/WPF/BocchiTracker.Client/App.xaml.cs
index dd70e9b..09fcd98 100644
--- a/Application/WPF/BocchiTracker.Client/App.xaml.cs
+++ b/Application/WPF/BocchiTracker.Client/App.xaml.cs
@@ -36,6 +36,7 @@
using BocchiTracker.Config;
using BocchiTracker.CrossServiceUploader;
using BocchiTracker.ModelEvent;
+using BocchiTracker.GameCaptureRTC;
namespace BocchiTracker.Client
{
@@ -56,6 +57,9 @@ protected override void OnExit(ExitEventArgs e)
var connection = Container.Resolve();
connection.Stop();
+ var recording = Container.Resolve();
+ recording.Stop();
+
base.OnExit(e);
}
@@ -128,6 +132,7 @@ protected override void OnInitialized()
var cacheProvider = Container.Resolve();
var connection = Container.Resolve();
+ var recording = Container.Resolve();
var dataRepository = Container.Resolve();
var issueInfoBundle = Container.Resolve();
var serviceClientFactory = Container.Resolve();
@@ -136,6 +141,7 @@ protected override void OnInitialized()
authConfigRepositoryFactory.Initialize(Path.Combine("Configs", nameof(AuthConfig) + "s"));
_ = connection.StartAsync(projectConfig.Port);
+ recording.Start(projectConfig.WebSocketPort, projectConfig.ExternalToolsPath.FFmpegPath, userConfig.CaptureSetting);
cacheProvider.SetCacheDirectory(string.IsNullOrEmpty(projectConfig.CacheDirectory) ? Path.GetTempPath() : projectConfig.CacheDirectory);
@@ -180,6 +186,11 @@ protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
moduleCatalog.AddModule();
moduleCatalog.AddModule();
moduleCatalog.AddModule();
+ moduleCatalog.AddModule(
+ dependsOn: new string[]
+ {
+ typeof(ConfigModule).Name
+ });
moduleCatalog.AddModule(
dependsOn: new string[]
{
diff --git a/Application/WPF/BocchiTracker.Client/BocchiTracker.Client.csproj b/Application/WPF/BocchiTracker.Client/BocchiTracker.Client.csproj
index 35faf96..b46bb7a 100644
--- a/Application/WPF/BocchiTracker.Client/BocchiTracker.Client.csproj
+++ b/Application/WPF/BocchiTracker.Client/BocchiTracker.Client.csproj
@@ -8,6 +8,8 @@
False
YutoArita
..\Artifact
+
+ x64
@@ -19,7 +21,7 @@
-
+
diff --git a/Application/WPF/BocchiTracker.Client/ViewModels/ReportParts/UtilityViewModel.cs b/Application/WPF/BocchiTracker.Client/ViewModels/ReportParts/UtilityViewModel.cs
index ca95b61..397b127 100644
--- a/Application/WPF/BocchiTracker.Client/ViewModels/ReportParts/UtilityViewModel.cs
+++ b/Application/WPF/BocchiTracker.Client/ViewModels/ReportParts/UtilityViewModel.cs
@@ -30,6 +30,7 @@
using BocchiTracker.Client.Share.Commands;
using BocchiTracker.ModelEvent;
using BocchiTracker.IssueAssetCollector.Handlers.Log;
+using BocchiTracker.IssueAssetCollector.Handlers.Movie;
namespace BocchiTracker.Client.ViewModels.ReportParts
{
@@ -39,6 +40,7 @@ public class UtilityViewModel : BindableBase
public ICommand TakeScreenshotCommand { get; private set; }
public ICommand CaptureCoredumpCommand { get; private set; }
+ public ICommand TakeMovieCommand { get; private set; }
public ICommand PostIssueCommand { get; private set; }
[Required(ErrorMessage = "Required")]
@@ -66,6 +68,7 @@ public UtilityViewModel(
{
TakeScreenshotCommand = new DelegateCommand(OnTakeScreenshot);
CaptureCoredumpCommand = new DelegateCommand(OnCaptureCoredump);
+ TakeMovieCommand = new AsyncCommand(OnTakeMovie);
PostIssueCommand = new AsyncCommand(OnPostIssue);
PostServices = new ReactiveCollection();
@@ -189,6 +192,22 @@ public void OnTakeScreenshot()
handler.Handle(_appStatusBundles.TrackerApplication, 0, _projectConfig.FileSaveDirectory);
}
+ public async Task OnTakeMovie()
+ {
+ if (_projectConfig == null)
+ return;
+
+ _eventAggregator.GetEvent().Publish(new ProgressEventParameter { Message = "Take movie" });
+ {
+ await Task.Run(() =>
+ {
+ var handler = _createActionHandler.Create(typeof(MovieHandler));
+ handler.Handle(_appStatusBundles.TrackerApplication, 0, _projectConfig.FileSaveDirectory);
+ });
+ }
+ _eventAggregator.GetEvent().Publish();
+ }
+
public void OnConnectedCreateHandle(AppStatusBundle inAppStatusBundle)
{
{
diff --git a/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigParts/MovieCaptureParts.cs b/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigParts/MovieCaptureParts.cs
new file mode 100644
index 0000000..978a39b
--- /dev/null
+++ b/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigParts/MovieCaptureParts.cs
@@ -0,0 +1,57 @@
+using BocchiTracker.Config.Configs;
+using BocchiTracker.Config;
+using BocchiTracker.ServiceClientAdapters;
+using Reactive.Bindings;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Prism.Mvvm;
+
+namespace BocchiTracker.Client.ViewModels.UserConfigParts
+{
+ public class MovieCaptureParts : BindableBase, IConfig
+ {
+ public ReactiveProperty NotUse { get; set; } = new ReactiveProperty(false);
+ public ReactiveProperty UseWebRTC { get; set; } = new ReactiveProperty(false);
+ public ReactiveProperty UseOBS { get; set; } = new ReactiveProperty(false);
+
+ private UserConfig _userConfig;
+
+ public void Initialize(CachedConfigRepository inUserConfigRepository, IAuthConfigRepositoryFactory inAuthConfigRepositoryFactory, ProjectConfig inProjectConfig)
+ {
+ Debug.Assert(inUserConfigRepository.Load() != null);
+
+ _userConfig = inUserConfigRepository.Load();
+ switch(_userConfig.CaptureSetting.GameCaptureType)
+ {
+ case GameCaptureType.NotUse: NotUse.Value = true; break;
+ case GameCaptureType.WebRTC: UseWebRTC.Value = true; break;
+ case GameCaptureType.OBSStudio: UseOBS.Value = true; break;
+ }
+ }
+
+ public void Save(ref bool outIsNeedRestart)
+ {
+ outIsNeedRestart |= true;
+
+ if (NotUse.Value)
+ {
+ _userConfig.CaptureSetting.GameCaptureType = GameCaptureType.NotUse;
+ return;
+ }
+ if (UseWebRTC.Value)
+ {
+ _userConfig.CaptureSetting.GameCaptureType = GameCaptureType.WebRTC;
+ return;
+ }
+ if (UseOBS.Value)
+ {
+ _userConfig.CaptureSetting.GameCaptureType = GameCaptureType.OBSStudio;
+ return;
+ }
+ }
+ }
+}
diff --git a/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigViewModel.cs b/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigViewModel.cs
index 4eaaac0..a4012be 100644
--- a/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigViewModel.cs
+++ b/Application/WPF/BocchiTracker.Client/ViewModels/UserConfigViewModel.cs
@@ -27,6 +27,7 @@ public class UserConfigViewModel : BindableBase
public UserConfigParts.AuthenticationParts AuthenticationParts { get; set; }
public UserConfigParts.ChoiceProjectConfigParts ChoiceProjectConfigParts { get; set; }
public UserConfigParts.MiscParts MiscParts { get; set; }
+ public UserConfigParts.MovieCaptureParts MovieCaptureParts { get; set; }
private readonly IEventAggregator _eventAggregator;
private IAuthConfigRepositoryFactory _authConfigRepository;
@@ -53,6 +54,7 @@ public UserConfigViewModel(
AuthenticationParts = new UserConfigParts.AuthenticationParts();
MiscParts = new UserConfigParts.MiscParts();
ChoiceProjectConfigParts = new UserConfigParts.ChoiceProjectConfigParts();
+ MovieCaptureParts = new UserConfigParts.MovieCaptureParts();
}
private void OnConfigReload(ConfigReloadEventParameter inParam)
@@ -61,14 +63,14 @@ private void OnConfigReload(ConfigReloadEventParameter inParam)
if (userConfig == null)
_userConfigRepository.Save(new UserConfig());
- foreach (var ui in new List { AuthenticationParts, ChoiceProjectConfigParts, MiscParts })
+ foreach (var ui in new List { AuthenticationParts, ChoiceProjectConfigParts, MiscParts, MovieCaptureParts })
ui.Initialize(_userConfigRepository, _authConfigRepository, _projectConfigRepository.Load());
}
public void OnSave()
{
bool isNeedRestart = false;
- foreach (var ui in new List { AuthenticationParts, ChoiceProjectConfigParts, MiscParts })
+ foreach (var ui in new List { AuthenticationParts, ChoiceProjectConfigParts, MiscParts, MovieCaptureParts })
ui.Save(ref isNeedRestart);
var config = _userConfigRepository.Load();
diff --git a/Application/WPF/BocchiTracker.Client/Views/ReportParts/UtilityView.xaml b/Application/WPF/BocchiTracker.Client/Views/ReportParts/UtilityView.xaml
index e0919bd..899f39f 100644
--- a/Application/WPF/BocchiTracker.Client/Views/ReportParts/UtilityView.xaml
+++ b/Application/WPF/BocchiTracker.Client/Views/ReportParts/UtilityView.xaml
@@ -36,6 +36,18 @@
+