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 @@ +