From 86baa476f4aedf921a43e392443aa66b265a30a9 Mon Sep 17 00:00:00 2001 From: Ivan Anikin Date: Wed, 20 May 2026 15:33:00 +0700 Subject: [PATCH] Refactoring --- .../src/com/AIGames/FunnyGirl/OBBData.java | 2 +- Config/DefaultEngine.ini | 3 + Content/Assets/Icon/Chat.uasset | 3 + Content/Assets/Icon/Girl.uasset | 3 + Content/Assets/Icon/Player.uasset | 3 + Content/Assets/Icon/T_Settingss.uasset | 4 +- .../Game/Blueprints/Girl/Rita/MP_Angry.uasset | 4 +- .../Blueprints/Girl/Rita/MP_AngryBurst.uasset | 4 +- .../Game/Blueprints/Girl/Rita/MP_Blink.uasset | 4 +- .../Girl/Rita/MP_ColdDeadStare.uasset | 4 +- .../Game/Blueprints/Girl/Rita/MP_Idle.uasset | 4 +- Content/Game/Blueprints/UI/WBP_Begin.uasset | 4 +- Content/Game/Blueprints/UI/WBP_Button.uasset | 4 +- Content/Game/Blueprints/UI/WBP_Chat.uasset | 4 +- Content/Game/Blueprints/UI/WBP_HUD.uasset | 4 +- Content/Game/Blueprints/UI/WBP_Main.uasset | 4 +- Content/Game/Blueprints/UI/WBP_Message.uasset | 3 + .../Blueprints/UI/WBP_MessageHistory.uasset | 3 + .../AndroidRadioPlayer.uplugin | 19 - .../Android/AndroidRadioPlayer_UPL.xml | 51 --- .../Android/Java/AndroidRadioPlayer.java | 274 ------------- .../AndroidRadioPlayer.Build.cs | 36 -- .../Private/AndroidRadioJavaBridge.cpp | 207 ---------- .../Private/AndroidRadioJavaBridge.h | 17 - .../Private/AndroidRadioMediaPlayer.cpp | 365 ------------------ .../Private/AndroidRadioMediaPlayer.h | 78 ---- .../Private/AndroidRadioPlayerBPLibrary.cpp | 74 ---- .../Private/AndroidRadioPlayerModule.cpp | 115 ------ .../Public/AndroidRadioPlayerBPLibrary.h | 54 --- Source/FunnyGirl/FunnyGirl.Build.cs | 2 +- .../Private/LLM/PersonaBrainComponent.cpp | 81 ++++ .../Morph/MorphControllerComponent.cpp | 108 +++--- .../FunnyGirlUtilityBlueprintLibrary.cpp | 14 + .../Public/LLM/PersonaBrainComponent.h | 27 ++ .../Public/Morph/MorphControllerComponent.h | 10 +- .../Public/Morph/MorphPresetDataAsset.h | 27 +- .../FunnyGirlUtilityBlueprintLibrary.h | 5 + .../MorphPresetDetailsCustomization.cpp | 4 - .../Editor/MorphPresetEditorController.cpp | 122 +----- .../Morph/Editor/MorphPresetViewportPanel.cpp | 41 +- .../Editor/MorphPresetEditorController.h | 5 +- 41 files changed, 287 insertions(+), 1513 deletions(-) create mode 100644 Content/Assets/Icon/Chat.uasset create mode 100644 Content/Assets/Icon/Girl.uasset create mode 100644 Content/Assets/Icon/Player.uasset create mode 100644 Content/Game/Blueprints/UI/WBP_Message.uasset create mode 100644 Content/Game/Blueprints/UI/WBP_MessageHistory.uasset delete mode 100644 Plugins/AndroidRadioPlayer/AndroidRadioPlayer.uplugin delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/AndroidRadioPlayer_UPL.xml delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/Java/AndroidRadioPlayer.java delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/AndroidRadioPlayer.Build.cs delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.cpp delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.h delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.cpp delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.h delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerBPLibrary.cpp delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerModule.cpp delete mode 100644 Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Public/AndroidRadioPlayerBPLibrary.h diff --git a/Build/Android/src/com/AIGames/FunnyGirl/OBBData.java b/Build/Android/src/com/AIGames/FunnyGirl/OBBData.java index f1ce1a1..dac9bf8 100644 --- a/Build/Android/src/com/AIGames/FunnyGirl/OBBData.java +++ b/Build/Android/src/com/AIGames/FunnyGirl/OBBData.java @@ -19,7 +19,7 @@ public static final XAPKFile[] xAPKS = { new XAPKFile( true, // true signifies a main file "1", // the version of the APK that the file was uploaded against -259567652L // the length of the file in bytes +259568643L // the length of the file in bytes ) }; }; diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 52da11c..85c6cee 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -1,5 +1,8 @@ +[/Script/Engine.Engine] ++ActiveClassRedirects=(OldClassName="/Script/AndroidRadioPlayer.AndroidRadioPlayerBPLibrary",NewClassName="/Script/FunnyGirl.FunnyGirlUtilityBlueprintLibrary") + [/Script/EngineSettings.GameMapsSettings] EditorStartupMap=/Game/Game/Maps/M_GirlHouse.M_GirlHouse LocalMapOptions= diff --git a/Content/Assets/Icon/Chat.uasset b/Content/Assets/Icon/Chat.uasset new file mode 100644 index 0000000..0f3cefc --- /dev/null +++ b/Content/Assets/Icon/Chat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e926f07e3c0e0ad745d70351e10abb0569486a610e813aa73d1d404ef422d71b +size 1258949 diff --git a/Content/Assets/Icon/Girl.uasset b/Content/Assets/Icon/Girl.uasset new file mode 100644 index 0000000..88f79b1 --- /dev/null +++ b/Content/Assets/Icon/Girl.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fcb5f346e1a141ff45d8fa45846b4ef9410a23096cebf0bfcd9147646e26443 +size 1275085 diff --git a/Content/Assets/Icon/Player.uasset b/Content/Assets/Icon/Player.uasset new file mode 100644 index 0000000..e640735 --- /dev/null +++ b/Content/Assets/Icon/Player.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bee856a3db5e7f602353d272e1154fd9642e29047fe1c838621977c51b07c356 +size 1286162 diff --git a/Content/Assets/Icon/T_Settingss.uasset b/Content/Assets/Icon/T_Settingss.uasset index 80a6795..8fa5f29 100644 --- a/Content/Assets/Icon/T_Settingss.uasset +++ b/Content/Assets/Icon/T_Settingss.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56f12d9bb96a8358d4a2de61f9171c0871a9925634ff8b0970b0436c3a36ca11 -size 12331 +oid sha256:36cc006c519082c1fffce52632e62806c7e818c9110ee7cbd1d80815c39ac3db +size 12479 diff --git a/Content/Game/Blueprints/Girl/Rita/MP_Angry.uasset b/Content/Game/Blueprints/Girl/Rita/MP_Angry.uasset index ee88cf8..80722ff 100644 --- a/Content/Game/Blueprints/Girl/Rita/MP_Angry.uasset +++ b/Content/Game/Blueprints/Girl/Rita/MP_Angry.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1117d11b13c81a209550a846cb5c9ca7808b9b1c08c5d6bf5960843c1e423b9d -size 6633 +oid sha256:90fbb61f7fc632e0fc9e95b5c6bb75263c125bdfa3ad9a15e539059fe1c3da58 +size 6385 diff --git a/Content/Game/Blueprints/Girl/Rita/MP_AngryBurst.uasset b/Content/Game/Blueprints/Girl/Rita/MP_AngryBurst.uasset index 8f76a76..fe4fd81 100644 --- a/Content/Game/Blueprints/Girl/Rita/MP_AngryBurst.uasset +++ b/Content/Game/Blueprints/Girl/Rita/MP_AngryBurst.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77cbae55509f6b73f715f366ac2ee28b055c75cf43eb72a2233a8b61e39f5fd2 -size 5978 +oid sha256:13cad6680570d32e430ee633262930fc86c0868de49cc60983e719e888a8ce74 +size 6480 diff --git a/Content/Game/Blueprints/Girl/Rita/MP_Blink.uasset b/Content/Game/Blueprints/Girl/Rita/MP_Blink.uasset index b6ebecf..95a67ad 100644 --- a/Content/Game/Blueprints/Girl/Rita/MP_Blink.uasset +++ b/Content/Game/Blueprints/Girl/Rita/MP_Blink.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37c755e9262480a6efd364f2c567cdb4783a00cd9d988cfdb3434dfce6e4343f -size 3647 +oid sha256:a04d2519f6e70d5b1f12b384d0b30cd5b57a9847a54eb576581278a93fd87bb6 +size 3994 diff --git a/Content/Game/Blueprints/Girl/Rita/MP_ColdDeadStare.uasset b/Content/Game/Blueprints/Girl/Rita/MP_ColdDeadStare.uasset index eb2a04b..0fc97e5 100644 --- a/Content/Game/Blueprints/Girl/Rita/MP_ColdDeadStare.uasset +++ b/Content/Game/Blueprints/Girl/Rita/MP_ColdDeadStare.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20ed2889c7ab894835d867d424e3dc89c698f5d5b9494096849d32c27868d801 -size 5962 +oid sha256:c528b487b5f016e4fc7d587ad4517242ab51febc9bdf432ed1ee0c2f3cbb0bed +size 6500 diff --git a/Content/Game/Blueprints/Girl/Rita/MP_Idle.uasset b/Content/Game/Blueprints/Girl/Rita/MP_Idle.uasset index 6b90080..4c52ba9 100644 --- a/Content/Game/Blueprints/Girl/Rita/MP_Idle.uasset +++ b/Content/Game/Blueprints/Girl/Rita/MP_Idle.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf7de421cb6754dc21c6581bac590579516327d4282ba799c7967fdc1a6135ee -size 7556 +oid sha256:308fc5543db3f8fafd5623b39f3d8cb3947157e493a9d7a0be312c4607884841 +size 7564 diff --git a/Content/Game/Blueprints/UI/WBP_Begin.uasset b/Content/Game/Blueprints/UI/WBP_Begin.uasset index dd17284..273b96a 100644 --- a/Content/Game/Blueprints/UI/WBP_Begin.uasset +++ b/Content/Game/Blueprints/UI/WBP_Begin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c49b0b2130df1e095ba4445c7261a10e7ceb85c6c7c9aaa3e794f113ec6208a -size 129475 +oid sha256:06cc0c88d219ff8bd0cfc0e9fdf17049766584fd471deaf48b7afbe8af0fb00b +size 126768 diff --git a/Content/Game/Blueprints/UI/WBP_Button.uasset b/Content/Game/Blueprints/UI/WBP_Button.uasset index 1bd190b..c711f0f 100644 --- a/Content/Game/Blueprints/UI/WBP_Button.uasset +++ b/Content/Game/Blueprints/UI/WBP_Button.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70654c1a13ebd98829c3c6df321f6cfdaacb6dd83921141587163056aee174aa -size 87138 +oid sha256:1b3bd008dbd9392b693f3893b03b40ce281169bb91c738ef43523f646e2f8abf +size 87296 diff --git a/Content/Game/Blueprints/UI/WBP_Chat.uasset b/Content/Game/Blueprints/UI/WBP_Chat.uasset index 6f5212a..d9f2edc 100644 --- a/Content/Game/Blueprints/UI/WBP_Chat.uasset +++ b/Content/Game/Blueprints/UI/WBP_Chat.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa19ba00aacc270633cad9ad0706fba09d12aa7cbf9251a686e9bece45864223 -size 246188 +oid sha256:9640acebc7fe51d1d605adb8dba73f798bc905ef9b6a320f6246e7151fa2b76b +size 256714 diff --git a/Content/Game/Blueprints/UI/WBP_HUD.uasset b/Content/Game/Blueprints/UI/WBP_HUD.uasset index 5c004eb..9580908 100644 --- a/Content/Game/Blueprints/UI/WBP_HUD.uasset +++ b/Content/Game/Blueprints/UI/WBP_HUD.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e4bd64dbe06708aead43df3065694dd76f6634cb4d0e2e844fa8fe7293cb195 -size 74176 +oid sha256:c5c4c38059f3a672a528ea1a3fea4f3d7f63ce54e8a638270cb46b74bda01a28 +size 76264 diff --git a/Content/Game/Blueprints/UI/WBP_Main.uasset b/Content/Game/Blueprints/UI/WBP_Main.uasset index 2798472..74157c2 100644 --- a/Content/Game/Blueprints/UI/WBP_Main.uasset +++ b/Content/Game/Blueprints/UI/WBP_Main.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f32fcb0710d7bd8842b025b7a8dcb396a505f34c93a550f06dd736d54b7c849 -size 79124 +oid sha256:ad9216651944e1c787f213c14776f468a0a30265e4acecf06468f7f17760209a +size 94500 diff --git a/Content/Game/Blueprints/UI/WBP_Message.uasset b/Content/Game/Blueprints/UI/WBP_Message.uasset new file mode 100644 index 0000000..31521be --- /dev/null +++ b/Content/Game/Blueprints/UI/WBP_Message.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e071f67a994729202c255991d574dfd9a8ac5df3d3cdaac447e8f84e60b25f92 +size 71486 diff --git a/Content/Game/Blueprints/UI/WBP_MessageHistory.uasset b/Content/Game/Blueprints/UI/WBP_MessageHistory.uasset new file mode 100644 index 0000000..6777ea1 --- /dev/null +++ b/Content/Game/Blueprints/UI/WBP_MessageHistory.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57780d3f89834e06bac702a7a647b7f524c7537ffd73810a2366b27c17bfbbad +size 166010 diff --git a/Plugins/AndroidRadioPlayer/AndroidRadioPlayer.uplugin b/Plugins/AndroidRadioPlayer/AndroidRadioPlayer.uplugin deleted file mode 100644 index 8123092..0000000 --- a/Plugins/AndroidRadioPlayer/AndroidRadioPlayer.uplugin +++ /dev/null @@ -1,19 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0.0", - "FriendlyName": "Android Radio Player", - "Description": "Blueprint radio playback for Android using AndroidX Media3 ExoPlayer.", - "Category": "Media Players", - "CreatedBy": "FunnyGirl", - "CanContainContent": false, - "IsBetaVersion": true, - "EnabledByDefault": true, - "Modules": [ - { - "Name": "AndroidRadioPlayer", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ] -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/AndroidRadioPlayer_UPL.xml b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/AndroidRadioPlayer_UPL.xml deleted file mode 100644 index 589dd6a..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/AndroidRadioPlayer_UPL.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - -dependencies { - implementation('androidx.media3:media3-exoplayer:1.3.1') - implementation('androidx.media3:media3-exoplayer-hls:1.3.1') -} - - - - - - - - - - --keep class com.epicgames.unreal.AndroidRadioPlayer { - public *; -} - - - - - - AndroidRadioPlayer.handleActivityPause(); - - - - - - AndroidRadioPlayer.handleActivityResume(); - - - - - - AndroidRadioPlayer.releaseRadio(); - - - diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/Java/AndroidRadioPlayer.java b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/Java/AndroidRadioPlayer.java deleted file mode 100644 index 25345ea..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Android/Java/AndroidRadioPlayer.java +++ /dev/null @@ -1,274 +0,0 @@ -package com.epicgames.unreal; - -import android.app.Activity; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import androidx.media3.common.AudioAttributes; -import androidx.media3.common.C; -import androidx.media3.common.MediaItem; -import androidx.media3.common.MimeTypes; -import androidx.media3.common.PlaybackException; -import androidx.media3.common.Player; -import androidx.media3.exoplayer.DefaultLoadControl; -import androidx.media3.exoplayer.ExoPlayer; - -public final class AndroidRadioPlayer { - private static final String TAG = "AndroidRadioPlayer"; - - private static final int STATE_IDLE = 0; - private static final int STATE_BUFFERING = 1; - private static final int STATE_READY = 2; - private static final int STATE_ENDED = 3; - private static final int STATE_ERROR = 4; - - private static ExoPlayer player; - private static volatile int playbackState = STATE_IDLE; - private static volatile String lastError = ""; - private static volatile boolean isPlaying = false; - private static volatile boolean shouldResumeAfterActivityPause = false; - private static volatile long cachedDurationMillis = 0; - private static volatile long cachedCurrentPositionMillis = 0; - private static float pendingVolume = 1.0f; - private static final Handler mainHandler = new Handler(Looper.getMainLooper()); - - private AndroidRadioPlayer() { - } - - public static boolean playRadio(final Activity activity, final String url, final float volume) { - if (activity == null) { - lastError = "Activity is null"; - playbackState = STATE_ERROR; - return false; - } - - if (url == null || url.trim().isEmpty()) { - lastError = "Radio URL is empty"; - playbackState = STATE_ERROR; - return false; - } - - pendingVolume = clampVolume(volume); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - try { - ensurePlayer(activity); - lastError = ""; - playbackState = STATE_BUFFERING; - player.stop(); - player.clearMediaItems(); - isPlaying = false; - player.setVolume(pendingVolume); - player.setMediaItem(buildMediaItem(url.trim())); - player.prepare(); - player.play(); - } catch (Throwable error) { - lastError = error.toString(); - playbackState = STATE_ERROR; - Log.e(TAG, "Failed to play radio", error); - } - } - }); - - return true; - } - - public static void stopRadio() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - releasePlayer(); - } - }); - } - - public static void releaseRadio() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - releasePlayer(); - } - }); - } - - public static void pauseRadio() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - if (player != null) { - player.pause(); - } - } - }); - } - - public static void resumeRadio() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - if (player != null) { - player.play(); - } - } - }); - } - - public static void setRadioVolume(final float volume) { - pendingVolume = clampVolume(volume); - runOnPlayerThread(new Runnable() { - @Override - public void run() { - if (player != null) { - player.setVolume(pendingVolume); - } - } - }); - } - - public static void handleActivityPause() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - shouldResumeAfterActivityPause = player != null && player.isPlaying(); - if (player != null && shouldResumeAfterActivityPause) { - player.pause(); - } - } - }); - } - - public static void handleActivityResume() { - runOnPlayerThread(new Runnable() { - @Override - public void run() { - if (player != null && shouldResumeAfterActivityPause) { - shouldResumeAfterActivityPause = false; - player.play(); - } - } - }); - } - - public static boolean isRadioPlaying() { - return isPlaying; - } - - public static int getPlaybackState() { - return playbackState; - } - - public static String getLastError() { - return lastError == null ? "" : lastError; - } - - public static long getDurationMillis() { - return cachedDurationMillis; - } - - public static long getCurrentPositionMillis() { - return cachedCurrentPositionMillis; - } - - private static void ensurePlayer(Activity activity) { - if (player != null) { - return; - } - - DefaultLoadControl loadControl = new DefaultLoadControl.Builder() - .setBufferDurationsMs(5000, 15000, 1000, 2000) - .build(); - - player = new ExoPlayer.Builder(activity.getApplicationContext()) - .setLoadControl(loadControl) - .setHandleAudioBecomingNoisy(true) - .build(); - - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) - .build(); - player.setAudioAttributes(audioAttributes, true); - player.setVolume(pendingVolume); - player.addListener(new Player.Listener() { - @Override - public void onPlaybackStateChanged(int state) { - if (state == Player.STATE_BUFFERING) { - playbackState = STATE_BUFFERING; - } else if (state == Player.STATE_READY) { - playbackState = STATE_READY; - long duration = player.getDuration(); - cachedDurationMillis = duration == C.TIME_UNSET ? 0 : duration; - } else if (state == Player.STATE_ENDED) { - playbackState = STATE_ENDED; - } else { - playbackState = STATE_IDLE; - } - } - - @Override - public void onIsPlayingChanged(boolean playing) { - isPlaying = playing; - } - - @Override - public void onPlayerError(PlaybackException error) { - lastError = error == null ? "Unknown ExoPlayer error" : error.toString(); - playbackState = STATE_ERROR; - isPlaying = false; - Log.e(TAG, "Playback error", error); - } - }); - } - - private static void releasePlayer() { - if (player != null) { - try { - player.stop(); - player.clearMediaItems(); - player.release(); - } catch (Throwable error) { - Log.w(TAG, "Failed to release radio player cleanly", error); - } - player = null; - } - shouldResumeAfterActivityPause = false; - isPlaying = false; - cachedDurationMillis = 0; - cachedCurrentPositionMillis = 0; - playbackState = STATE_IDLE; - } - - private static MediaItem buildMediaItem(String url) { - MediaItem.Builder builder = new MediaItem.Builder().setUri(Uri.parse(url)); - String lowerUrl = url.toLowerCase(); - - if (lowerUrl.contains(".m3u8") || lowerUrl.contains("m3u8")) { - builder.setMimeType(MimeTypes.APPLICATION_M3U8); - } else if (lowerUrl.contains("aacp") || lowerUrl.contains(".aac") || lowerUrl.contains("audio/aac")) { - builder.setMimeType(MimeTypes.AUDIO_AAC); - } else if (lowerUrl.contains(".mp3") || lowerUrl.contains("mp3")) { - builder.setMimeType(MimeTypes.AUDIO_MPEG); - } else if (lowerUrl.contains(".ogg") || lowerUrl.contains("opus")) { - builder.setMimeType(MimeTypes.AUDIO_OGG); - } - - return builder.build(); - } - - private static void runOnPlayerThread(Runnable runnable) { - mainHandler.post(runnable); - } - - private static float clampVolume(float volume) { - if (volume < 0.0f) { - return 0.0f; - } - if (volume > 1.0f) { - return 1.0f; - } - return volume; - } -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/AndroidRadioPlayer.Build.cs b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/AndroidRadioPlayer.Build.cs deleted file mode 100644 index a22385f..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/AndroidRadioPlayer.Build.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using UnrealBuildTool; - -public class AndroidRadioPlayer : ModuleRules -{ - public AndroidRadioPlayer(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicDependencyModuleNames.AddRange(new string[] - { - "Core", - "CoreUObject", - "Engine", - "Media", - "MediaAssets" - }); - - PrivateDependencyModuleNames.AddRange(new string[] - { - "Projects" - }); - - if (Target.Platform == UnrealTargetPlatform.Android) - { - PrivateDependencyModuleNames.AddRange(new string[] - { - "ApplicationCore", - "Launch" - }); - - string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath); - AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "Android", "AndroidRadioPlayer_UPL.xml")); - } - } -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.cpp b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.cpp deleted file mode 100644 index b0115e9..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "AndroidRadioJavaBridge.h" - -#if PLATFORM_ANDROID -#include "Android/AndroidApplication.h" -#include "Android/AndroidJavaEnv.h" -#include "Android/AndroidJNI.h" -#endif - -namespace AndroidRadioJavaBridge -{ -#if PLATFORM_ANDROID - jclass GetJavaClass(JNIEnv* Env) - { - static jclass RadioPlayerClass = FAndroidApplication::FindJavaClassGlobalRef("com/epicgames/unreal/AndroidRadioPlayer"); - if (RadioPlayerClass == nullptr) - { - FAndroidApplication::CheckJavaException(); - UE_LOG(LogTemp, Error, TEXT("AndroidRadioPlayer Java class was not found.")); - } - return RadioPlayerClass; - } - - jmethodID GetStaticMethod(JNIEnv* Env, const ANSICHAR* MethodName, const ANSICHAR* Signature) - { - jclass RadioPlayerClass = GetJavaClass(Env); - if (RadioPlayerClass == nullptr) - { - return nullptr; - } - - jmethodID Method = FJavaWrapper::FindStaticMethod(Env, RadioPlayerClass, MethodName, Signature, false); - if (Method == nullptr) - { - FAndroidApplication::CheckJavaException(); - UE_LOG(LogTemp, Error, TEXT("AndroidRadioPlayer Java method was not found: %s"), ANSI_TO_TCHAR(MethodName)); - } - return Method; - } -#endif - - bool Play(const FString& Url, float Volume) - { -#if PLATFORM_ANDROID - if (Url.IsEmpty()) - { - return false; - } - - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - if (Env == nullptr) - { - return false; - } - - jclass RadioPlayerClass = GetJavaClass(Env); - jmethodID Method = GetStaticMethod(Env, "playRadio", "(Landroid/app/Activity;Ljava/lang/String;F)Z"); - if (RadioPlayerClass == nullptr || Method == nullptr) - { - return false; - } - - auto JavaUrl = FJavaHelper::ToJavaString(Env, Url); - const jfloat ClampedVolume = FMath::Clamp(Volume, 0.0f, 1.0f); - const jboolean Result = Env->CallStaticBooleanMethod(RadioPlayerClass, Method, FJavaWrapper::GameActivityThis, *JavaUrl, ClampedVolume); - FAndroidApplication::CheckJavaException(); - return Result == JNI_TRUE; -#else - return false; -#endif - } - - void Stop() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "stopRadio", "()V") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - Env->CallStaticVoidMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - } -#endif - } - - void Pause() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "pauseRadio", "()V") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - Env->CallStaticVoidMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - } -#endif - } - - void Resume() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "resumeRadio", "()V") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - Env->CallStaticVoidMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - } -#endif - } - - void SetVolume(float Volume) - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "setRadioVolume", "(F)V") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - Env->CallStaticVoidMethod(RadioPlayerClass, Method, FMath::Clamp(Volume, 0.0f, 1.0f)); - FAndroidApplication::CheckJavaException(); - } -#endif - } - - bool IsPlaying() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "isRadioPlaying", "()Z") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - const jboolean Result = Env->CallStaticBooleanMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - return Result == JNI_TRUE; - } -#endif - return false; - } - - int32 GetPlaybackState() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "getPlaybackState", "()I") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - const jint State = Env->CallStaticIntMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - return static_cast(State); - } -#endif - return -1; - } - - FString GetLastError() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "getLastError", "()Ljava/lang/String;") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - jstring Error = static_cast(Env->CallStaticObjectMethod(RadioPlayerClass, Method)); - FAndroidApplication::CheckJavaException(); - return FJavaHelper::FStringFromLocalRef(Env, Error); - } -#endif - return FString(); - } - - int64 GetDurationMillis() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "getDurationMillis", "()J") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - const jlong Duration = Env->CallStaticLongMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - return static_cast(Duration); - } -#endif - return 0; - } - - int64 GetCurrentPositionMillis() - { -#if PLATFORM_ANDROID - JNIEnv* Env = FAndroidApplication::GetJavaEnv(); - jclass RadioPlayerClass = Env ? GetJavaClass(Env) : nullptr; - jmethodID Method = Env ? GetStaticMethod(Env, "getCurrentPositionMillis", "()J") : nullptr; - if (RadioPlayerClass != nullptr && Method != nullptr) - { - const jlong Position = Env->CallStaticLongMethod(RadioPlayerClass, Method); - FAndroidApplication::CheckJavaException(); - return static_cast(Position); - } -#endif - return 0; - } -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.h b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.h deleted file mode 100644 index 4116a5d..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioJavaBridge.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" - -namespace AndroidRadioJavaBridge -{ - bool Play(const FString& Url, float Volume); - void Stop(); - void Pause(); - void Resume(); - void SetVolume(float Volume); - bool IsPlaying(); - int32 GetPlaybackState(); - FString GetLastError(); - int64 GetDurationMillis(); - int64 GetCurrentPositionMillis(); -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.cpp b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.cpp deleted file mode 100644 index 1475be5..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.cpp +++ /dev/null @@ -1,365 +0,0 @@ -#include "AndroidRadioMediaPlayer.h" - -#include "AndroidRadioJavaBridge.h" -#include "IMediaEventSink.h" -#include "Misc/Guid.h" - -namespace -{ - constexpr int32 JavaStateIdle = 0; - constexpr int32 JavaStateBuffering = 1; - constexpr int32 JavaStateReady = 2; - constexpr int32 JavaStateEnded = 3; - constexpr int32 JavaStateError = 4; -} - -FAndroidRadioMediaPlayer::FAndroidRadioMediaPlayer(IMediaEventSink& InEventSink) - : EventSink(InEventSink) - , CurrentState(EMediaState::Closed) - , CurrentStatus(EMediaStatus::None) - , CurrentRate(0.0f) - , CurrentVolume(1.0f) - , bLooping(false) - , bHasSentOpenEvent(false) - , bHasSentBufferingEvent(false) - , bHasSentEndEvent(false) -{ -} - -FAndroidRadioMediaPlayer::~FAndroidRadioMediaPlayer() -{ - Close(); -} - -void FAndroidRadioMediaPlayer::Close() -{ - if (CurrentState != EMediaState::Closed) - { - AndroidRadioJavaBridge::Stop(); - CurrentUrl.Empty(); - CurrentState = EMediaState::Closed; - CurrentStatus = EMediaStatus::None; - CurrentRate = 0.0f; - bHasSentOpenEvent = false; - bHasSentBufferingEvent = false; - bHasSentEndEvent = false; - EventSink.ReceiveMediaEvent(EMediaEvent::MediaClosed); - } -} - -IMediaCache& FAndroidRadioMediaPlayer::GetCache() -{ - return *this; -} - -IMediaControls& FAndroidRadioMediaPlayer::GetControls() -{ - return *this; -} - -FString FAndroidRadioMediaPlayer::GetInfo() const -{ - return FString::Printf(TEXT("Android Radio Player\nUrl: %s\nState: %d\nLast error: %s"), *CurrentUrl, static_cast(CurrentState), *AndroidRadioJavaBridge::GetLastError()); -} - -FGuid FAndroidRadioMediaPlayer::GetPlayerPluginGUID() const -{ - static FGuid PlayerPluginGUID(0x2547a7af, 0xe9504c2f, 0xa090e1b7, 0x26c7d716); - return PlayerPluginGUID; -} - -IMediaSamples& FAndroidRadioMediaPlayer::GetSamples() -{ - return *this; -} - -FString FAndroidRadioMediaPlayer::GetStats() const -{ - return FString(); -} - -IMediaTracks& FAndroidRadioMediaPlayer::GetTracks() -{ - return *this; -} - -FString FAndroidRadioMediaPlayer::GetUrl() const -{ - return CurrentUrl; -} - -IMediaView& FAndroidRadioMediaPlayer::GetView() -{ - return *this; -} - -bool FAndroidRadioMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) -{ - Close(); - - if (!Url.StartsWith(TEXT("http://")) && !Url.StartsWith(TEXT("https://"))) - { - CurrentState = EMediaState::Error; - return false; - } - - CurrentUrl = Url; - CurrentState = EMediaState::Preparing; - CurrentStatus = EMediaStatus::Connecting | EMediaStatus::Buffering; - CurrentRate = 1.0f; - bHasSentOpenEvent = false; - bHasSentBufferingEvent = false; - bHasSentEndEvent = false; - EventSink.ReceiveMediaEvent(EMediaEvent::MediaConnecting); - - if (!AndroidRadioJavaBridge::Play(Url, CurrentVolume)) - { - CurrentState = EMediaState::Error; - CurrentStatus = EMediaStatus::None; - CurrentRate = 0.0f; - EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpenFailed); - return false; - } - - return true; -} - -bool FAndroidRadioMediaPlayer::Open(const TSharedRef& Archive, const FString& OriginalUrl, const IMediaOptions* Options) -{ - return false; -} - -void FAndroidRadioMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode) -{ - UpdateStateFromJava(); -} - -bool FAndroidRadioMediaPlayer::SetNativeVolume(float Volume) -{ - CurrentVolume = FMath::Clamp(Volume, 0.0f, 1.0f); - AndroidRadioJavaBridge::SetVolume(CurrentVolume); - return true; -} - -bool FAndroidRadioMediaPlayer::CanControl(EMediaControl Control) const -{ - return Control == EMediaControl::Pause || Control == EMediaControl::Resume; -} - -FTimespan FAndroidRadioMediaPlayer::GetDuration() const -{ - const int64 DurationMillis = AndroidRadioJavaBridge::GetDurationMillis(); - return DurationMillis > 0 ? FTimespan::FromMilliseconds(DurationMillis) : FTimespan::Zero(); -} - -float FAndroidRadioMediaPlayer::GetRate() const -{ - return CurrentRate; -} - -EMediaState FAndroidRadioMediaPlayer::GetState() const -{ - return CurrentState; -} - -EMediaStatus FAndroidRadioMediaPlayer::GetStatus() const -{ - return CurrentStatus; -} - -TRangeSet FAndroidRadioMediaPlayer::GetSupportedRates(EMediaRateThinning Thinning) const -{ - TRangeSet Result; - Result.Add(TRange(0.0f)); - Result.Add(TRange(1.0f)); - return Result; -} - -FTimespan FAndroidRadioMediaPlayer::GetTime() const -{ - return FTimespan::FromMilliseconds(AndroidRadioJavaBridge::GetCurrentPositionMillis()); -} - -bool FAndroidRadioMediaPlayer::IsLooping() const -{ - return bLooping; -} - -bool FAndroidRadioMediaPlayer::Seek(const FTimespan& Time) -{ - return false; -} - -bool FAndroidRadioMediaPlayer::SetLooping(bool Looping) -{ - bLooping = Looping; - return true; -} - -bool FAndroidRadioMediaPlayer::SetRate(float Rate) -{ - if (CurrentState == EMediaState::Closed || CurrentState == EMediaState::Error) - { - return false; - } - - if (FMath::IsNearlyZero(Rate)) - { - AndroidRadioJavaBridge::Pause(); - CurrentRate = 0.0f; - CurrentState = EMediaState::Paused; - EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended); - return true; - } - - if (FMath::IsNearlyEqual(Rate, 1.0f)) - { - AndroidRadioJavaBridge::Resume(); - CurrentRate = 1.0f; - CurrentState = EMediaState::Playing; - EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed); - return true; - } - - return false; -} - -bool FAndroidRadioMediaPlayer::GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex, FMediaAudioTrackFormat& OutFormat) const -{ - if (TrackIndex == 0 && FormatIndex == 0) - { - OutFormat.BitsPerSample = 16; - OutFormat.NumChannels = 2; - OutFormat.SampleRate = 44100; - OutFormat.TypeName = TEXT("External"); - return true; - } - return false; -} - -int32 FAndroidRadioMediaPlayer::GetNumTracks(EMediaTrackType TrackType) const -{ - return TrackType == EMediaTrackType::Audio ? 1 : 0; -} - -int32 FAndroidRadioMediaPlayer::GetNumTrackFormats(EMediaTrackType TrackType, int32 TrackIndex) const -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0 ? 1 : 0; -} - -int32 FAndroidRadioMediaPlayer::GetSelectedTrack(EMediaTrackType TrackType) const -{ - return TrackType == EMediaTrackType::Audio ? 0 : INDEX_NONE; -} - -FText FAndroidRadioMediaPlayer::GetTrackDisplayName(EMediaTrackType TrackType, int32 TrackIndex) const -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0 ? NSLOCTEXT("AndroidRadioMediaPlayer", "AudioTrackName", "Radio") : FText::GetEmpty(); -} - -int32 FAndroidRadioMediaPlayer::GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0 ? 0 : INDEX_NONE; -} - -FString FAndroidRadioMediaPlayer::GetTrackLanguage(EMediaTrackType TrackType, int32 TrackIndex) const -{ - return TEXT("und"); -} - -FString FAndroidRadioMediaPlayer::GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0 ? TEXT("Radio") : FString(); -} - -bool FAndroidRadioMediaPlayer::GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex, FMediaVideoTrackFormat& OutFormat) const -{ - return false; -} - -bool FAndroidRadioMediaPlayer::SelectTrack(EMediaTrackType TrackType, int32 TrackIndex) -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0; -} - -bool FAndroidRadioMediaPlayer::SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex, int32 FormatIndex) -{ - return TrackType == EMediaTrackType::Audio && TrackIndex == 0 && FormatIndex == 0; -} - -bool FAndroidRadioMediaPlayer::PeekVideoSampleTime(FMediaTimeStamp& TimeStamp) -{ - return false; -} - -void FAndroidRadioMediaPlayer::UpdateStateFromJava() -{ - if (CurrentState == EMediaState::Closed || CurrentUrl.IsEmpty()) - { - return; - } - - const int32 JavaState = AndroidRadioJavaBridge::GetPlaybackState(); - const bool bIsPlaying = AndroidRadioJavaBridge::IsPlaying(); - - if (JavaState == JavaStateError) - { - if (CurrentState != EMediaState::Error) - { - CurrentState = EMediaState::Error; - CurrentStatus = EMediaStatus::None; - CurrentRate = 0.0f; - EventSink.ReceiveMediaEvent(bHasSentOpenEvent ? EMediaEvent::PlaybackSuspended : EMediaEvent::MediaOpenFailed); - } - return; - } - - if (JavaState == JavaStateBuffering) - { - CurrentState = EMediaState::Preparing; - CurrentStatus = EMediaStatus::Buffering; - if (!bHasSentBufferingEvent) - { - bHasSentBufferingEvent = true; - EventSink.ReceiveMediaEvent(EMediaEvent::MediaBuffering); - } - return; - } - - if (JavaState == JavaStateReady) - { - if (!bHasSentOpenEvent) - { - bHasSentOpenEvent = true; - EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpened); - EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged); - EventSink.ReceiveMediaEvent(EMediaEvent::MediaBufferingComplete); - } - - CurrentStatus = EMediaStatus::None; - CurrentState = bIsPlaying ? EMediaState::Playing : EMediaState::Paused; - CurrentRate = bIsPlaying ? 1.0f : 0.0f; - bHasSentBufferingEvent = false; - return; - } - - if (JavaState == JavaStateEnded) - { - CurrentState = EMediaState::Stopped; - CurrentStatus = EMediaStatus::None; - CurrentRate = 0.0f; - if (!bHasSentEndEvent) - { - bHasSentEndEvent = true; - EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackEndReached); - } - return; - } - - if (JavaState == JavaStateIdle) - { - CurrentState = CurrentUrl.IsEmpty() ? EMediaState::Closed : EMediaState::Stopped; - CurrentStatus = EMediaStatus::None; - CurrentRate = 0.0f; - } -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.h b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.h deleted file mode 100644 index 5f8d677..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioMediaPlayer.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "IMediaCache.h" -#include "IMediaControls.h" -#include "IMediaPlayer.h" -#include "IMediaSamples.h" -#include "IMediaTracks.h" -#include "IMediaView.h" - -class IMediaEventSink; - -class FAndroidRadioMediaPlayer - : public IMediaPlayer - , protected IMediaCache - , protected IMediaControls - , protected IMediaSamples - , protected IMediaTracks - , protected IMediaView -{ -public: - explicit FAndroidRadioMediaPlayer(IMediaEventSink& InEventSink); - virtual ~FAndroidRadioMediaPlayer() override; - - virtual void Close() override; - virtual IMediaCache& GetCache() override; - virtual IMediaControls& GetControls() override; - virtual FString GetInfo() const override; - virtual FGuid GetPlayerPluginGUID() const override; - virtual IMediaSamples& GetSamples() override; - virtual FString GetStats() const override; - virtual IMediaTracks& GetTracks() override; - virtual FString GetUrl() const override; - virtual IMediaView& GetView() override; - virtual bool Open(const FString& Url, const IMediaOptions* Options) override; - virtual bool Open(const TSharedRef& Archive, const FString& OriginalUrl, const IMediaOptions* Options) override; - virtual void TickInput(FTimespan DeltaTime, FTimespan Timecode) override; - virtual bool SetNativeVolume(float Volume) override; - - virtual bool CanControl(EMediaControl Control) const override; - virtual FTimespan GetDuration() const override; - virtual float GetRate() const override; - virtual EMediaState GetState() const override; - virtual EMediaStatus GetStatus() const override; - virtual TRangeSet GetSupportedRates(EMediaRateThinning Thinning) const override; - virtual FTimespan GetTime() const override; - virtual bool IsLooping() const override; - virtual bool Seek(const FTimespan& Time) override; - virtual bool SetLooping(bool Looping) override; - virtual bool SetRate(float Rate) override; - - virtual bool GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex, FMediaAudioTrackFormat& OutFormat) const override; - virtual int32 GetNumTracks(EMediaTrackType TrackType) const override; - virtual int32 GetNumTrackFormats(EMediaTrackType TrackType, int32 TrackIndex) const override; - virtual int32 GetSelectedTrack(EMediaTrackType TrackType) const override; - virtual FText GetTrackDisplayName(EMediaTrackType TrackType, int32 TrackIndex) const override; - virtual int32 GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const override; - virtual FString GetTrackLanguage(EMediaTrackType TrackType, int32 TrackIndex) const override; - virtual FString GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const override; - virtual bool GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex, FMediaVideoTrackFormat& OutFormat) const override; - virtual bool SelectTrack(EMediaTrackType TrackType, int32 TrackIndex) override; - virtual bool SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex, int32 FormatIndex) override; - - virtual bool PeekVideoSampleTime(FMediaTimeStamp& TimeStamp) override; - -private: - void UpdateStateFromJava(); - - IMediaEventSink& EventSink; - FString CurrentUrl; - EMediaState CurrentState; - EMediaStatus CurrentStatus; - float CurrentRate; - float CurrentVolume; - bool bLooping; - bool bHasSentOpenEvent; - bool bHasSentBufferingEvent; - bool bHasSentEndEvent; -}; diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerBPLibrary.cpp b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerBPLibrary.cpp deleted file mode 100644 index 78ccefa..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerBPLibrary.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "AndroidRadioPlayerBPLibrary.h" - -#include "AndroidRadioJavaBridge.h" - -bool UAndroidRadioPlayerBPLibrary::OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url) -{ - if (MediaPlayer == nullptr || StreamMediaSource == nullptr || !Url.Contains(TEXT("://"))) - { - return false; - } - - StreamMediaSource->StreamUrl = Url; - MediaPlayer->Close(); - return MediaPlayer->OpenSource(StreamMediaSource); -} - -bool UAndroidRadioPlayerBPLibrary::PlayRadio(const FString& Url, float Volume) -{ - return AndroidRadioJavaBridge::Play(Url, Volume); -} - -void UAndroidRadioPlayerBPLibrary::StopRadio() -{ - AndroidRadioJavaBridge::Stop(); -} - -void UAndroidRadioPlayerBPLibrary::PauseRadio() -{ - AndroidRadioJavaBridge::Pause(); -} - -void UAndroidRadioPlayerBPLibrary::ResumeRadio() -{ - AndroidRadioJavaBridge::Resume(); -} - -void UAndroidRadioPlayerBPLibrary::SetRadioVolume(float Volume) -{ - AndroidRadioJavaBridge::SetVolume(Volume); -} - -bool UAndroidRadioPlayerBPLibrary::IsRadioPlaying() -{ - return AndroidRadioJavaBridge::IsPlaying(); -} - -EAndroidRadioPlaybackState UAndroidRadioPlayerBPLibrary::GetRadioPlaybackState() -{ - switch (AndroidRadioJavaBridge::GetPlaybackState()) - { - case 0: - return EAndroidRadioPlaybackState::Idle; - case 1: - return EAndroidRadioPlaybackState::Buffering; - case 2: - return EAndroidRadioPlaybackState::Ready; - case 3: - return EAndroidRadioPlaybackState::Ended; - case 4: - return EAndroidRadioPlaybackState::Error; - default: - return EAndroidRadioPlaybackState::Unsupported; - } -} - -FString UAndroidRadioPlayerBPLibrary::GetRadioLastError() -{ - return AndroidRadioJavaBridge::GetLastError(); -} - -bool UAndroidRadioPlayerBPLibrary::IsAndroidRadioPlayerSupported() -{ - return PLATFORM_ANDROID; -} diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerModule.cpp b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerModule.cpp deleted file mode 100644 index 171540a..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Private/AndroidRadioPlayerModule.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "AndroidRadioMediaPlayer.h" - -#include "IMediaModule.h" -#include "IMediaPlayerFactory.h" -#include "Misc/Paths.h" -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE "FAndroidRadioPlayerModule" - -class FAndroidRadioPlayerModule - : public IModuleInterface - , public IMediaPlayerFactory -{ -public: - virtual void StartupModule() override - { - SupportedPlatforms.Add(TEXT("Android")); - SupportedUriSchemes.Add(TEXT("http")); - SupportedUriSchemes.Add(TEXT("https")); - - if (IMediaModule* MediaModule = FModuleManager::LoadModulePtr("Media")) - { - MediaModule->RegisterPlayerFactory(*this); - } - } - - virtual void ShutdownModule() override - { - if (IMediaModule* MediaModule = FModuleManager::GetModulePtr("Media")) - { - MediaModule->UnregisterPlayerFactory(*this); - } - } - - virtual bool CanPlayUrl(const FString& Url, const IMediaOptions* Options, TArray* OutWarnings, TArray* OutErrors) const override - { - return GetPlayabilityConfidenceScore(Url, Options, OutWarnings, OutErrors) > 0; - } - - virtual int32 GetPlayabilityConfidenceScore(const FString& Url, const IMediaOptions* Options, TArray* OutWarnings, TArray* OutErrors) const override - { - FString Scheme; - FString Location; - - if (!Url.Split(TEXT("://"), &Scheme, &Location, ESearchCase::CaseSensitive)) - { - if (OutErrors != nullptr) - { - OutErrors->Add(LOCTEXT("NoSchemeFound", "No URI scheme found")); - } - return 0; - } - - if (!SupportedUriSchemes.Contains(Scheme)) - { - if (OutErrors != nullptr) - { - OutErrors->Add(FText::Format(LOCTEXT("SchemeNotSupported", "The URI scheme '{0}' is not supported"), FText::FromString(Scheme))); - } - return 0; - } - - const FString Extension = FPaths::GetExtension(Location, false).ToLower(); - if (Extension.IsEmpty() || Extension == TEXT("mp3") || Extension == TEXT("aac") || Extension == TEXT("m3u8") || Extension == TEXT("pls") || Extension == TEXT("ogg") || Extension == TEXT("opus")) - { - return 90; - } - - if (OutWarnings != nullptr) - { - OutWarnings->Add(FText::Format(LOCTEXT("ExtensionProbablyNotRadio", "The file extension '{0}' is not a known radio stream extension"), FText::FromString(Extension))); - } - return 20; - } - - virtual TSharedPtr CreatePlayer(IMediaEventSink& EventSink) override - { - return MakeShared(EventSink); - } - - virtual FText GetDisplayName() const override - { - return LOCTEXT("MediaPlayerDisplayName", "Android Radio Player"); - } - - virtual FName GetPlayerName() const override - { - static FName PlayerName(TEXT("AndroidRadioPlayer")); - return PlayerName; - } - - virtual FGuid GetPlayerPluginGUID() const override - { - static FGuid PlayerPluginGUID(0x2547a7af, 0xe9504c2f, 0xa090e1b7, 0x26c7d716); - return PlayerPluginGUID; - } - - virtual const TArray& GetSupportedPlatforms() const override - { - return SupportedPlatforms; - } - - virtual bool SupportsFeature(EMediaFeature Feature) const override - { - return Feature == EMediaFeature::AudioTracks; - } - -private: - TArray SupportedPlatforms; - TArray SupportedUriSchemes; -}; - -IMPLEMENT_MODULE(FAndroidRadioPlayerModule, AndroidRadioPlayer) - -#undef LOCTEXT_NAMESPACE diff --git a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Public/AndroidRadioPlayerBPLibrary.h b/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Public/AndroidRadioPlayerBPLibrary.h deleted file mode 100644 index 8199b8e..0000000 --- a/Plugins/AndroidRadioPlayer/Source/AndroidRadioPlayer/Public/AndroidRadioPlayerBPLibrary.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "Kismet/BlueprintFunctionLibrary.h" -#include "MediaPlayer.h" -#include "StreamMediaSource.h" -#include "AndroidRadioPlayerBPLibrary.generated.h" - -UENUM(BlueprintType) -enum class EAndroidRadioPlaybackState : uint8 -{ - Idle UMETA(DisplayName = "Idle"), - Buffering UMETA(DisplayName = "Buffering"), - Ready UMETA(DisplayName = "Ready"), - Ended UMETA(DisplayName = "Ended"), - Error UMETA(DisplayName = "Error"), - Unsupported UMETA(DisplayName = "Unsupported") -}; - -UCLASS() -class ANDROIDRADIOPLAYER_API UAndroidRadioPlayerBPLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category = "Android Radio Player|Media") - static bool OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url); - - UFUNCTION(BlueprintCallable, Category = "Android Radio Player") - static bool PlayRadio(const FString& Url, float Volume = 1.0f); - - UFUNCTION(BlueprintCallable, Category = "Android Radio Player") - static void StopRadio(); - - UFUNCTION(BlueprintCallable, Category = "Android Radio Player") - static void PauseRadio(); - - UFUNCTION(BlueprintCallable, Category = "Android Radio Player") - static void ResumeRadio(); - - UFUNCTION(BlueprintCallable, Category = "Android Radio Player") - static void SetRadioVolume(float Volume); - - UFUNCTION(BlueprintPure, Category = "Android Radio Player") - static bool IsRadioPlaying(); - - UFUNCTION(BlueprintPure, Category = "Android Radio Player") - static EAndroidRadioPlaybackState GetRadioPlaybackState(); - - UFUNCTION(BlueprintPure, Category = "Android Radio Player") - static FString GetRadioLastError(); - - UFUNCTION(BlueprintPure, Category = "Android Radio Player") - static bool IsAndroidRadioPlayerSupported(); -}; diff --git a/Source/FunnyGirl/FunnyGirl.Build.cs b/Source/FunnyGirl/FunnyGirl.Build.cs index e5b86cc..c5cb02f 100644 --- a/Source/FunnyGirl/FunnyGirl.Build.cs +++ b/Source/FunnyGirl/FunnyGirl.Build.cs @@ -8,7 +8,7 @@ public class FunnyGirl : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "UMG" }); + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "MediaAssets", "UMG" }); PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); diff --git a/Source/FunnyGirl/Private/LLM/PersonaBrainComponent.cpp b/Source/FunnyGirl/Private/LLM/PersonaBrainComponent.cpp index f2fca42..51d8516 100644 --- a/Source/FunnyGirl/Private/LLM/PersonaBrainComponent.cpp +++ b/Source/FunnyGirl/Private/LLM/PersonaBrainComponent.cpp @@ -169,6 +169,87 @@ bool UPersonaBrainComponent::GetLastSavedLLMMessage(FString& OutMessage) const return false; } +TArray UPersonaBrainComponent::GetSavedChatMessages() const +{ + TArray ChatMessages; + + const FString FilePath = GetChatFilePath(); + FString ChatJson; + if (!FFileHelper::LoadFileToString(ChatJson, *FilePath)) + { + return ChatMessages; + } + + TSharedPtr RootObject; + TSharedRef> Reader = TJsonReaderFactory<>::Create(ChatJson); + if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid()) + { + return ChatMessages; + } + + const TArray>* SavedMessages = nullptr; + if (!RootObject->TryGetArrayField(TEXT("messages"), SavedMessages) || !SavedMessages) + { + return ChatMessages; + } + + const FString GirlSpeaker = Identity && !Identity->Name.IsEmpty() && !Identity->Name.ToString().TrimStartAndEnd().IsEmpty() + ? Identity->Name.ToString().TrimStartAndEnd() + : FString(TEXT("Girl")); + + for (const TSharedPtr& MessageValue : *SavedMessages) + { + const TSharedPtr* MessageObject = nullptr; + if (!MessageValue.IsValid() || !MessageValue->TryGetObject(MessageObject) || !MessageObject || !MessageObject->IsValid()) + { + continue; + } + + FString Role; + FString Content; + if (!(*MessageObject)->TryGetStringField(TEXT("role"), Role) || !(*MessageObject)->TryGetStringField(TEXT("content"), Content)) + { + continue; + } + + if ((Role != TEXT("user") && Role != TEXT("assistant")) || Content.TrimStartAndEnd().IsEmpty()) + { + continue; + } + + FPersonaChatMessage ChatMessage; + (*MessageObject)->TryGetStringField(TEXT("timestamp"), ChatMessage.Timestamp); + ChatMessage.Role = Role; + ChatMessage.Speaker = Role == TEXT("assistant") ? GirlSpeaker : FString(TEXT("Player")); + ChatMessage.Content = Content; + + const TSharedPtr InteractActionValue = (*MessageObject)->TryGetField(TEXT("interact_action")); + if (InteractActionValue.IsValid() && InteractActionValue->Type != EJson::Null) + { + if (InteractActionValue->Type == EJson::Object) + { + const TSharedPtr* InteractActionObject = nullptr; + if (InteractActionValue->TryGetObject(InteractActionObject) && InteractActionObject && InteractActionObject->IsValid()) + { + FString InteractActionJson; + TSharedRef> Writer = TJsonWriterFactory<>::Create(&InteractActionJson); + FJsonSerializer::Serialize(InteractActionObject->ToSharedRef(), Writer); + ChatMessage.InteractActionJson = InteractActionJson; + } + } + else + { + InteractActionValue->TryGetString(ChatMessage.InteractActionJson); + } + } + + (*MessageObject)->TryGetStringField(TEXT("emotion"), ChatMessage.Emotion); + ChatMessages.Add(ChatMessage); + } + + return ChatMessages; +} + void UPersonaBrainComponent::BroadcastBrainError(const FString& ErrorMessage) const { OnLLMError.Broadcast(ErrorMessage); diff --git a/Source/FunnyGirl/Private/Morph/MorphControllerComponent.cpp b/Source/FunnyGirl/Private/Morph/MorphControllerComponent.cpp index cb94c37..df597ff 100644 --- a/Source/FunnyGirl/Private/Morph/MorphControllerComponent.cpp +++ b/Source/FunnyGirl/Private/Morph/MorphControllerComponent.cpp @@ -2,8 +2,6 @@ #include "Morph/MorphControllerComponent.h" #include "Morph/MorphPresetDataAsset.h" -#include "Animation/AnimInstance.h" -#include "Animation/AnimMontage.h" #include "Components/SkeletalMeshComponent.h" #include "GameFramework/Character.h" #include "Engine/World.h" @@ -37,6 +35,12 @@ void UMorphControllerComponent::BeginPlay() Super::BeginPlay(); } +void UMorphControllerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + ClearMorphsImmediately(); + Super::EndPlay(EndPlayReason); +} + void UMorphControllerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); @@ -212,7 +216,6 @@ bool UMorphControllerComponent::AddPreset(UMorphPresetDataAsset* Preset, float W if (Slot.Preset == Preset) { Slot.TargetWeight = FMath::Clamp(Weight, 0.0f, 1.0f); - Slot.CurrentWeight = Slot.TargetWeight; Slot.PlaybackTime = 0.0f; Slot.LoopDelayRemaining = 0.0f; Slot.TimelineDuration = TimelineDuration; @@ -224,7 +227,7 @@ bool UMorphControllerComponent::AddPreset(UMorphPresetDataAsset* Preset, float W FActiveMorphPresetSlot NewSlot; NewSlot.Preset = Preset; NewSlot.TargetWeight = FMath::Clamp(Weight, 0.0f, 1.0f); - NewSlot.CurrentWeight = NewSlot.TargetWeight; + NewSlot.CurrentWeight = 0.0f; NewSlot.PlaybackTime = 0.0f; NewSlot.LoopDelayRemaining = 0.0f; NewSlot.TimelineDuration = TimelineDuration; @@ -239,78 +242,32 @@ bool UMorphControllerComponent::PlayPreset(UMorphPresetDataAsset* Preset, float { if (bLogWarnings) { - UE_LOG(LogMorphController, Warning, TEXT("PlayPreset ignored on %s: preset is null or has no morph clips or animation montage."), *GetNameSafe(GetOwner())); + UE_LOG(LogMorphController, Warning, TEXT("PlayPreset ignored on %s: preset is null or has no morph clips."), *GetNameSafe(GetOwner())); } return false; } - const bool bPlayedMorphs = Preset->IsValidPreset() ? AddPreset(Preset, Weight) : false; - const bool bPlayedAnimation = PlayPresetAnimation(Preset); - return bPlayedMorphs || bPlayedAnimation; -} - -bool UMorphControllerComponent::PlayPresetAnimation(UMorphPresetDataAsset* Preset) -{ - if (!Preset || !Preset->AnimationMontage) - { - return false; - } - - USkeletalMeshComponent* Mesh = ResolveMeshComponent(); - if (!Mesh) - { - if (bLogWarnings) - { - UE_LOG(LogMorphController, Warning, TEXT("Preset animation ignored on %s: no SkeletalMeshComponent was found."), *GetNameSafe(GetOwner())); - } - return false; - } - - UAnimInstance* AnimInstance = Mesh->GetAnimInstance(); - if (!AnimInstance) - { - if (bLogWarnings) - { - UE_LOG(LogMorphController, Warning, TEXT("Preset animation ignored on %s: mesh %s has no AnimInstance."), *GetNameSafe(GetOwner()), *GetNameSafe(Mesh)); - } - return false; - } - - UAnimMontage* Montage = Preset->AnimationMontage; - if (Preset->bRestartAnimationOnReplay && AnimInstance->Montage_IsPlaying(Montage)) - { - AnimInstance->Montage_Stop(0.0f, Montage); - } - - const float MontageLength = AnimInstance->Montage_Play(Montage, FMath::Max(Preset->AnimationPlayRate, 0.01f)); - if (MontageLength <= 0.0f) - { - if (bLogWarnings) - { - UE_LOG(LogMorphController, Warning, TEXT("Preset animation failed on %s: montage %s could not play. Check the Anim Blueprint slot setup."), *GetNameSafe(GetOwner()), *GetNameSafe(Montage)); - } - return false; - } - - if (!Preset->AnimationStartSection.IsNone()) - { - AnimInstance->Montage_JumpToSection(Preset->AnimationStartSection, Montage); - } - - return true; + return AddPreset(Preset, Weight); } void UMorphControllerComponent::RemovePreset(UMorphPresetDataAsset* Preset) { if (!Preset) return; + bool bRemovedAny = false; for (int32 i = ActivePresets.Num() - 1; i >= 0; --i) { if (ActivePresets[i].Preset == Preset) { ActivePresets.RemoveAt(i); + bRemovedAny = true; } } + + if (bRemovedAny) + { + UpdateMorphTargets(0.0f); + } } void UMorphControllerComponent::SetPresetWeight(UMorphPresetDataAsset* Preset, float Weight) @@ -336,6 +293,7 @@ void UMorphControllerComponent::SetActivePresets(const TArrayIsValidPreset()) { FActiveMorphPresetSlot NewSlot = Slot; + NewSlot.TargetWeight = FMath::Clamp(NewSlot.TargetWeight, 0.0f, 1.0f); NewSlot.CurrentWeight = 0.0f; NewSlot.PlaybackTime = 0.0f; NewSlot.LoopDelayRemaining = 0.0f; @@ -346,11 +304,14 @@ void UMorphControllerComponent::SetActivePresets(const TArray UMorphControllerComponent::GetActivePresets() con void UMorphControllerComponent::SetMorphTarget(FName MorphTargetName, float Value) { + if (MorphTargetName.IsNone()) + { + return; + } + DirectMorphOverrides.Add(MorphTargetName, ClampMorphTargetValue(Value)); + UpdateMorphTargets(0.0f); } void UMorphControllerComponent::RemoveMorphTargetOverride(FName MorphTargetName) { - DirectMorphOverrides.Remove(MorphTargetName); + if (DirectMorphOverrides.Remove(MorphTargetName) > 0) + { + UpdateMorphTargets(0.0f); + } } void UMorphControllerComponent::ClearDirectMorphOverrides() { DirectMorphOverrides.Empty(); + UpdateMorphTargets(0.0f); } void UMorphControllerComponent::SetLipSyncMorphValues(const TMap& MorphValues) @@ -405,11 +376,13 @@ void UMorphControllerComponent::SetLipSyncMorphValues(const TMap& LipSyncMorphValues.Add(Pair.Key, ClampMorphTargetValue(Pair.Value)); } } + UpdateMorphTargets(0.0f); } void UMorphControllerComponent::ClearLipSyncMorphValues() { LipSyncMorphValues.Empty(); + UpdateMorphTargets(0.0f); } void UMorphControllerComponent::UpdateMorphTargetsNow(float DeltaTime) @@ -501,6 +474,19 @@ void UMorphControllerComponent::UpdateMorphTargets(float DeltaTime) // 2. Gather final morph values. Overlapping clips use the strongest contribution. TMap FinalValues; + float ActivePresetWeightSum = 0.0f; + for (const FActiveMorphPresetSlot& Slot : ActivePresets) + { + if (Slot.Preset && Slot.CurrentWeight >= 0.001f && Slot.LoopDelayRemaining <= 0.0f) + { + ActivePresetWeightSum += Slot.CurrentWeight; + } + } + + const float PresetLayerScale = bNormalizePresetLayerWeights && MaxCombinedPresetWeight > 0.0f && ActivePresetWeightSum > MaxCombinedPresetWeight + ? MaxCombinedPresetWeight / ActivePresetWeightSum + : 1.0f; + for (const FActiveMorphPresetSlot& Slot : ActivePresets) { if (!Slot.Preset || Slot.CurrentWeight < 0.001f || Slot.LoopDelayRemaining > 0.0f) @@ -508,7 +494,7 @@ void UMorphControllerComponent::UpdateMorphTargets(float DeltaTime) continue; } - MorphPresetRuntime::AccumulatePresetMorphValuesAtTime(*Slot.Preset, Slot.PlaybackTime, Slot.CurrentWeight, FinalValues); + MorphPresetRuntime::AccumulatePresetMorphValuesAtTime(*Slot.Preset, Slot.PlaybackTime, Slot.CurrentWeight * PresetLayerScale, FinalValues); } // 3. Apply direct overrides with the same max-based priority rule. diff --git a/Source/FunnyGirl/Private/Utility/FunnyGirlUtilityBlueprintLibrary.cpp b/Source/FunnyGirl/Private/Utility/FunnyGirlUtilityBlueprintLibrary.cpp index 0eca430..beaad78 100644 --- a/Source/FunnyGirl/Private/Utility/FunnyGirlUtilityBlueprintLibrary.cpp +++ b/Source/FunnyGirl/Private/Utility/FunnyGirlUtilityBlueprintLibrary.cpp @@ -9,11 +9,13 @@ #include "Engine/SkeletalMesh.h" #include "Engine/Texture2D.h" #include "HAL/FileManager.h" +#include "MediaPlayer.h" #include "Misc/Base64.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonWriter.h" +#include "StreamMediaSource.h" namespace { @@ -358,3 +360,15 @@ bool UFunnyGirlUtilityBlueprintLibrary::DeleteLightString(const FString& Field, SaveObject->RemoveField(EncodedField); return SaveLightSaveObject(SaveObject.ToSharedRef(), OutError); } + +bool UFunnyGirlUtilityBlueprintLibrary::OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url) +{ + if (!MediaPlayer || !StreamMediaSource || !Url.Contains(TEXT("://"))) + { + return false; + } + + StreamMediaSource->StreamUrl = Url; + MediaPlayer->Close(); + return MediaPlayer->OpenSource(StreamMediaSource); +} diff --git a/Source/FunnyGirl/Public/LLM/PersonaBrainComponent.h b/Source/FunnyGirl/Public/LLM/PersonaBrainComponent.h index 660cace..3aa7547 100644 --- a/Source/FunnyGirl/Public/LLM/PersonaBrainComponent.h +++ b/Source/FunnyGirl/Public/LLM/PersonaBrainComponent.h @@ -28,6 +28,30 @@ struct FUNNYGIRL_API FLLMReplyResponse FString Emotion; }; +USTRUCT(BlueprintType) +struct FUNNYGIRL_API FPersonaChatMessage +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History") + FString Timestamp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History") + FString Role; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History") + FString Speaker; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History") + FString Content; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History|Interaction") + FString InteractActionJson; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History|Emotion") + FString Emotion; +}; + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMResponseDelegate, const FLLMReplyResponse&, Response); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMReplyStreamChunkDelegate, const FString&, ReplyChunk); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMErrorDelegate, const FString&, ErrorMessage); @@ -85,6 +109,9 @@ public: UFUNCTION(BlueprintPure, Category = "LLM|History") bool GetLastSavedLLMMessage(FString& OutMessage) const; + UFUNCTION(BlueprintPure, Category = "LLM|History") + TArray GetSavedChatMessages() const; + virtual void CancelRequest() override; protected: diff --git a/Source/FunnyGirl/Public/Morph/MorphControllerComponent.h b/Source/FunnyGirl/Public/Morph/MorphControllerComponent.h index 55ef84b..a135caf 100644 --- a/Source/FunnyGirl/Public/Morph/MorphControllerComponent.h +++ b/Source/FunnyGirl/Public/Morph/MorphControllerComponent.h @@ -74,6 +74,14 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller") bool bLogWarnings = true; + /** Keep layered presets from overdriving the face when several expressions are active at once. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Layering") + bool bNormalizePresetLayerWeights = true; + + /** Maximum total expression weight shared by active presets when normalization is enabled. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Layering", meta = (ClampMin = "0", ClampMax = "5")) + float MaxCombinedPresetWeight = 1.0f; + /** Emotion presets available for AI/director prompting. Use these as the character's facial expression vocabulary. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Emotions") TArray> EmotionPresets; @@ -178,6 +186,7 @@ public: protected: virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; private: UPROPERTY() @@ -197,7 +206,6 @@ private: USkeletalMeshComponent* ResolveMeshComponent() const; bool PresetHasAnyPositiveCurveValue(const UMorphPresetDataAsset& Preset) const; - bool PlayPresetAnimation(UMorphPresetDataAsset* Preset); void UpdateMorphTargets(float DeltaTime); void BlendMorphLayer(const TMap& LayerValues, float LayerWeight, EMorphLayerBlendMode BlendMode, TMap& InOutFinalValues) const; void ApplyFinalMorphValues(const TMap& FinalValues); diff --git a/Source/FunnyGirl/Public/Morph/MorphPresetDataAsset.h b/Source/FunnyGirl/Public/Morph/MorphPresetDataAsset.h index b43e4bb..c02b039 100644 --- a/Source/FunnyGirl/Public/Morph/MorphPresetDataAsset.h +++ b/Source/FunnyGirl/Public/Morph/MorphPresetDataAsset.h @@ -8,8 +8,6 @@ #include "Engine/SkeletalMesh.h" #include "MorphPresetDataAsset.generated.h" -class UAnimMontage; - USTRUCT(BlueprintType) struct FUNNYGIRL_API FMorphTimelineClip { @@ -77,26 +75,6 @@ public: ClampMin = "0")) float BlendSpeedOverride = 0.0f; - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = ( - DisplayName = "Animation Montage", - ToolTip = "Optional animation montage played when this preset starts. The montage must use a slot that is present in the character Anim Blueprint.")) - TObjectPtr AnimationMontage = nullptr; - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = ( - DisplayName = "Animation Play Rate", - ClampMin = "0.01")) - float AnimationPlayRate = 1.0f; - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = ( - DisplayName = "Start Section", - ToolTip = "Optional montage section to jump to after playback starts.")) - FName AnimationStartSection; - - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = ( - DisplayName = "Restart Animation On Replay", - ToolTip = "If enabled, an already playing montage is stopped and restarted when this preset is triggered again.")) - bool bRestartAnimationOnReplay = true; - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset") int32 GetMorphCount() const; @@ -104,10 +82,7 @@ public: float GetTimelineLength() const; UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset") - bool HasAnimation() const { return AnimationMontage != nullptr; } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset") - bool HasPlayableContent() const { return IsValidPreset() || HasAnimation(); } + bool HasPlayableContent() const { return IsValidPreset(); } UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset") bool IsValidPreset() const { return GetMorphCount() > 0; } diff --git a/Source/FunnyGirl/Public/Utility/FunnyGirlUtilityBlueprintLibrary.h b/Source/FunnyGirl/Public/Utility/FunnyGirlUtilityBlueprintLibrary.h index 3d1d6ff..ab1a6b4 100644 --- a/Source/FunnyGirl/Public/Utility/FunnyGirlUtilityBlueprintLibrary.h +++ b/Source/FunnyGirl/Public/Utility/FunnyGirlUtilityBlueprintLibrary.h @@ -8,6 +8,8 @@ class USkeletalMesh; class UButton; +class UMediaPlayer; +class UStreamMediaSource; class UTexture2D; UENUM(BlueprintType) @@ -53,4 +55,7 @@ public: UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Save") static bool DeleteLightString(const FString& Field, FString& OutError); + + UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Media") + static bool OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url); }; diff --git a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetDetailsCustomization.cpp b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetDetailsCustomization.cpp index 7fa7844..812a9f5 100644 --- a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetDetailsCustomization.cpp +++ b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetDetailsCustomization.cpp @@ -64,10 +64,6 @@ namespace TargetPreset.bLoop = SourcePreset.bLoop; TargetPreset.LoopDelay = SourcePreset.LoopDelay; TargetPreset.BlendSpeedOverride = SourcePreset.BlendSpeedOverride; - TargetPreset.AnimationMontage = SourcePreset.AnimationMontage; - TargetPreset.AnimationPlayRate = SourcePreset.AnimationPlayRate; - TargetPreset.AnimationStartSection = SourcePreset.AnimationStartSection; - TargetPreset.bRestartAnimationOnReplay = SourcePreset.bRestartAnimationOnReplay; } } diff --git a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetEditorController.cpp b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetEditorController.cpp index 8c36ee8..fe461ed 100644 --- a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetEditorController.cpp +++ b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetEditorController.cpp @@ -4,10 +4,7 @@ #include "Animation/AnimData/IAnimationDataController.h" #include "AnimPreviewInstance.h" -#include "Animation/AnimMontage.h" #include "Animation/AnimSequence.h" -#include "Animation/AnimSequenceBase.h" -#include "Animation/AnimationAsset.h" #include "Animation/DebugSkelMeshComponent.h" #include "Animation/Skeleton.h" #include "Components/SkeletalMeshComponent.h" @@ -115,15 +112,7 @@ float FMorphPresetEditorController::GetTimelineLength() const { if (const UMorphPresetDataAsset* PresetAsset = Preset.Get()) { - float TimelineLength = PresetAsset->GetTimelineLength(); - if (PresetAsset->AnimationMontage) - { - const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f); - const UAnimSequenceBase* PreviewSequence = PresetAsset->AnimationMontage->GetFirstAnimReference(); - const float AnimationLength = PreviewSequence ? PreviewSequence->GetPlayLength() : PresetAsset->AnimationMontage->GetPlayLength(); - TimelineLength = FMath::Max(TimelineLength, AnimationLength / PlayRate); - } - return FMath::Max(TimelineLength, 0.1f); + return FMath::Max(PresetAsset->GetTimelineLength(), 0.1f); } return 1.0f; @@ -145,7 +134,7 @@ void FMorphPresetEditorController::SetCurrentTime(float InTime) if (PreviewMeshComponent->PreviewInstance) { PreviewMeshComponent->PreviewInstance->SetPlaying(false); - PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false); + PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false); RefreshPreviewAnimationPose(); } } @@ -225,20 +214,21 @@ void FMorphPresetEditorController::NotifyPresetEdited(bool bBroadcastPresetChang PresetAsset->MarkPackageDirty(); } - UAnimationAsset* DesiredPreviewAsset = ResolvePreviewAnimationAsset(); - UAnimationAsset* CurrentPreviewAsset = nullptr; - if (TSharedPtr PreviewScene = GetPreviewScene()) + if (ActivePreviewMesh.Get() != GetPreviewMesh()) { - CurrentPreviewAsset = PreviewScene->GetPreviewAnimationAsset(); + RebuildPreviewScene(); + if (bBroadcastPresetChanged) + { + PresetChanged.Broadcast(); + } + return; } - if (DesiredPreviewAsset != CurrentPreviewAsset || - (DesiredPreviewAsset && PreviewAnimation.IsValid()) || - (!DesiredPreviewAsset && !PreviewAnimation.IsValid())) + if (!PreviewAnimation.IsValid()) { RebuildPreviewAnimation(); } - else if (PreviewAnimation.IsValid()) + else { SetAnimSequencePlayLength(*PreviewAnimation, GetTimelineLength(), false); } @@ -436,6 +426,7 @@ void FMorphPresetEditorController::RebuildPreviewScene() PreviewAnimation.Reset(); USkeletalMesh* PreviewMesh = GetPreviewMesh(); + ActivePreviewMesh = PreviewMesh; if (PreviewMesh == nullptr) { PreviewSceneChanged.Broadcast(); @@ -464,56 +455,29 @@ void FMorphPresetEditorController::RebuildPreviewAnimation() PreviewAnimation.Reset(); PreviewStatusText = FText::GetEmpty(); - UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset(); - if (PreviewAsset && !IsPreviewAnimationCompatible(PreviewAsset)) + PreviewAnimation = TStrongObjectPtr(AnimSequence); + SetAnimSequencePlayLength(*AnimSequence, GetTimelineLength(), false); + if (USkeletalMesh* PreviewMesh = GetPreviewMesh()) { - const USkeletalMesh* PreviewMesh = GetPreviewMesh(); - const FString PreviewMeshSkeletonName = PreviewMesh && PreviewMesh->GetSkeleton() - ? PreviewMesh->GetSkeleton()->GetPathName() - : TEXT("None"); - const FString AnimationSkeletonName = PreviewAsset->GetSkeleton() - ? PreviewAsset->GetSkeleton()->GetPathName() - : TEXT("None"); - - PreviewStatusText = FText::FromString(FString::Printf( - TEXT("Animation preview is disabled: montage sequence '%s' uses skeleton '%s', but Preview Mesh uses '%s'. Retarget the animation to the Preview Mesh skeleton."), - *GetNameSafe(PreviewAsset), - *AnimationSkeletonName, - *PreviewMeshSkeletonName)); - UE_LOG(LogMorphPresetEditorPreview, Warning, TEXT("%s"), *PreviewStatusText.ToString()); - PreviewAsset = nullptr; - } - - if (!PreviewAsset) - { - PreviewAnimation = TStrongObjectPtr(AnimSequence); - PreviewAsset = AnimSequence; - - SetAnimSequencePlayLength(*AnimSequence, GetTimelineLength(), false); - if (USkeletalMesh* PreviewMesh = GetPreviewMesh()) + if (USkeleton* Skeleton = PreviewMesh->GetSkeleton()) { - if (USkeleton* Skeleton = PreviewMesh->GetSkeleton()) - { - AnimSequence->SetSkeleton(Skeleton); - } + AnimSequence->SetSkeleton(Skeleton); } } - PreviewScene->SetPreviewAnimationAsset(PreviewAsset, true); + PreviewScene->SetPreviewAnimationAsset(AnimSequence, true); if (UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent()) { PreviewMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode); if (PreviewMeshComponent->PreviewInstance) { - const UMorphPresetDataAsset* PresetAsset = Preset.Get(); - const float PlayRate = PresetAsset ? FMath::Max(PresetAsset->AnimationPlayRate, 0.01f) : 1.0f; - PreviewMeshComponent->PreviewInstance->SetAnimationAsset(PreviewAsset, false, PlayRate); + PreviewMeshComponent->PreviewInstance->SetAnimationAsset(AnimSequence, false, 1.0f); PreviewMeshComponent->PreviewInstance->EnableControllers(true); PreviewMeshComponent->PreviewInstance->SetSkeletalControlAlpha(1.0f); PreviewMeshComponent->PreviewInstance->SetPlaying(false); PreviewMeshComponent->PreviewInstance->SetLooping(false); - PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false); + PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false); RefreshPreviewAnimationPose(); } } @@ -539,52 +503,6 @@ void FMorphPresetEditorController::RefreshPreviewAnimationPose() PreviewScene->InvalidateViews(); } -UAnimationAsset* FMorphPresetEditorController::ResolvePreviewAnimationAsset() const -{ - const UMorphPresetDataAsset* PresetAsset = Preset.Get(); - if (PresetAsset && PresetAsset->AnimationMontage) - { - if (UAnimSequenceBase* FirstAnimReference = PresetAsset->AnimationMontage->GetFirstAnimReference()) - { - return FirstAnimReference; - } - } - - return nullptr; -} - -float FMorphPresetEditorController::GetPreviewAnimationPosition() const -{ - const UMorphPresetDataAsset* PresetAsset = Preset.Get(); - UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset(); - if (!PresetAsset || !PreviewAsset) - { - return CurrentTime; - } - - const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f); - float PreviewPosition = CurrentTime * PlayRate; - if (const UAnimSequenceBase* PreviewSequence = Cast(PreviewAsset)) - { - return FMath::Clamp(PreviewPosition, 0.0f, PreviewSequence->GetPlayLength()); - } - - return PreviewPosition; -} - -bool FMorphPresetEditorController::IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const -{ - if (!PreviewAsset) - { - return true; - } - - const USkeletalMesh* PreviewMesh = GetPreviewMesh(); - const USkeleton* PreviewSkeleton = PreviewMesh ? PreviewMesh->GetSkeleton() : nullptr; - const USkeleton* AnimationSkeleton = PreviewAsset->GetSkeleton(); - return PreviewSkeleton && AnimationSkeleton && PreviewSkeleton->IsCompatibleForEditor(AnimationSkeleton); -} - void FMorphPresetEditorController::ApplyPresetToPreview() { const UMorphPresetDataAsset* PresetAsset = Preset.Get(); diff --git a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetViewportPanel.cpp b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetViewportPanel.cpp index 762e53a..1a7fede 100644 --- a/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetViewportPanel.cpp +++ b/Source/FunnyGirlEditor/Private/Morph/Editor/MorphPresetViewportPanel.cpp @@ -13,7 +13,9 @@ #include "PropertyCustomizationHelpers.h" #include "SEditorViewport.h" #include "Sound/SoundBase.h" +#include "ToolMenus.h" #include "Toolkits/AssetEditorToolkit.h" +#include "ViewportToolbar/UnrealEdViewportToolbar.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SSlider.h" @@ -118,11 +120,13 @@ namespace public: SLATE_BEGIN_ARGS(SMorphPresetPreviewViewport) {} SLATE_ARGUMENT(TSharedPtr, Controller) + SLATE_ARGUMENT(TWeakPtr, AssetEditorToolkit) SLATE_END_ARGS() void Construct(const FArguments& InArgs) { Controller = InArgs._Controller; + AssetEditorToolkit = InArgs._AssetEditorToolkit; SEditorViewport::Construct(SEditorViewport::FArguments()); } @@ -146,11 +150,45 @@ namespace virtual TSharedPtr BuildViewportToolbar() override { - return SNullWidget::NullWidget; + const FName ViewportToolbarName = TEXT("MorphPresetEditor.ViewportToolbar"); + if (!UToolMenus::Get()->IsMenuRegistered(ViewportToolbarName)) + { + UToolMenu* const ViewportToolbarMenu = UToolMenus::Get()->RegisterMenu( + ViewportToolbarName, + NAME_None, + EMultiBoxType::SlimHorizontalToolBar); + + ViewportToolbarMenu->StyleName = TEXT("ViewportToolbar"); + + FToolMenuSection& LeftSection = ViewportToolbarMenu->AddSection(TEXT("Left")); + LeftSection.AddEntry(UE::UnrealEd::CreateTransformsSubmenu()); + LeftSection.AddEntry(UE::UnrealEd::CreateSnappingSubmenu()); + + FToolMenuSection& RightSection = ViewportToolbarMenu->AddSection(TEXT("Right")); + RightSection.Alignment = EToolMenuSectionAlign::Last; + RightSection.AddEntry(UE::UnrealEd::CreateCameraSubmenu(UE::UnrealEd::FViewportCameraMenuOptions().ShowAll())); + RightSection.AddEntry(UE::UnrealEd::CreateViewModesSubmenu()); + RightSection.AddEntry(UE::UnrealEd::CreatePerformanceAndScalabilitySubmenu()); + } + + FToolMenuContext ViewportToolbarContext; + if (TSharedPtr PreviewScene = Controller.IsValid() ? Controller->GetPreviewScene() : nullptr) + { + ViewportToolbarContext.AppendCommandList(PreviewScene->GetCommandList()); + } + ViewportToolbarContext.AppendCommandList(GetCommandList()); + + UUnrealEdViewportToolbarContext* const ContextObject = + UE::UnrealEd::CreateViewportToolbarDefaultContext(SharedThis(this)); + ContextObject->AssetEditorToolkit = AssetEditorToolkit; + ViewportToolbarContext.AddObject(ContextObject); + + return UToolMenus::Get()->GenerateWidget(ViewportToolbarName, ViewportToolbarContext); } private: TSharedPtr Controller; + TWeakPtr AssetEditorToolkit; }; } @@ -330,6 +368,7 @@ void SMorphPresetViewportPanel::RebuildContent() [ SNew(SMorphPresetPreviewViewport) .Controller(Controller) + .AssetEditorToolkit(AssetEditorToolkit) ]); } diff --git a/Source/FunnyGirlEditor/Public/Morph/Editor/MorphPresetEditorController.h b/Source/FunnyGirlEditor/Public/Morph/Editor/MorphPresetEditorController.h index b47bc54..6179634 100644 --- a/Source/FunnyGirlEditor/Public/Morph/Editor/MorphPresetEditorController.h +++ b/Source/FunnyGirlEditor/Public/Morph/Editor/MorphPresetEditorController.h @@ -8,7 +8,6 @@ class FUICommandList; class AActor; class IPersonaPreviewScene; class IPersonaToolkit; -class UAnimationAsset; class UAnimSequence; class ULipSyncComponent; class ULipSyncMappingDataAsset; @@ -68,9 +67,6 @@ private: void RebuildPreviewAnimation(); void ApplyPresetToPreview(); void RefreshPreviewAnimationPose(); - UAnimationAsset* ResolvePreviewAnimationAsset() const; - float GetPreviewAnimationPosition() const; - bool IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const; void RebuildLipSyncPreviewComponents(); void DestroyLipSyncPreviewComponents(); void TickLipSyncPreviewComponents(float DeltaTime); @@ -82,6 +78,7 @@ private: TSharedPtr CommandList; TStrongObjectPtr PreviewAnimation; TStrongObjectPtr DefaultLipSyncPreviewMapping; + TWeakObjectPtr ActivePreviewMesh; TWeakObjectPtr LipSyncPreviewMapping; TWeakObjectPtr LipSyncPreviewSound; TWeakObjectPtr LipSyncPreviewActor;