khanhuitse05 / speech-and-text-unity-ios-android

Speed to text in Unity iOS use Native Speech Recognition
MIT License
286 stars 124 forks source link

Unity plugin speechToText not working when isShowPopupAndroid = false #82

Open ghKoty opened 1 year ago

ghKoty commented 1 year ago

Some information:

If isShowPopupAndroid = true it's working fine, but if isShowPopupAndroid = false it's not working:

None in console(i used debugger), game not crashing, no recording start sound

TheLLspectre commented 1 year ago

Hi !

I had the same problem with isShowPopupAndroid = false. And for now i fixed it. I'm not on the same version as you.

I had to investigate this problem. And it seams when you're not using the popup, google services can't manage errors.

Thanks to the adb debugger I found multiple exception with a runtime exception --> CRASH.

Like this one:

image

So I go in the plugin with Android Studio and manage exception for now.

public class MainActivity extends UnityPlayerActivity
{
    private TextToSpeech tts;
    private SpeechRecognizer speech;
    private Intent intent;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tts = new TextToSpeech(this, initListener);

        speech = SpeechRecognizer.createSpeechRecognizer(this);
        speech.setRecognitionListener(recognitionListener);
    }
    @Override
    public void onDestroy() {
        // Don't forget to shutdown tts!
        if (tts != null) {
            tts.stop();
            tts.shutdown();
        }
        if (speech != null) {
            speech.destroy();
        }
        super.onDestroy();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && null != data) {
            try
            {
                ArrayList<String> text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                UnityPlayer.UnitySendMessage("SpeechToText", "onResults", text.get(0));
            }
            catch(NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onPartialResults " + e.getMessage());
            }
        }
    }

    // speech to text
    public void OnStartRecording() {
        intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        //intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 2000);
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 2000);
        //intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 2000);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
        //onLog("test");

        /*Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, Long.valueOf(5000));
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, Long.valueOf(3000));
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, Long.valueOf(3000));
        if (!prompt.equals("")) intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
        UnityPlayer.currentActivity.startActivityForResult(intent, RESULT_SPEECH);*/

        this.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                speech.startListening(intent);
            }
        });
        UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "CallStart, Language:" + Bridge.languageSpeech);
    }
    public void OnStopRecording() {
        this.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                speech.stopListening();
            }
        });
        UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "CallStop");
    }

    public void onLog(String m)
    {
        UnityPlayer.UnitySendMessage("SpeechToText", "onLog", m.toString());
    }

    RecognitionListener recognitionListener = new RecognitionListener() {

        @Override
        public void onReadyForSpeech(Bundle params) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onReadyForSpeech", params.toString());
        }
        @Override
        public void onBeginningOfSpeech() {
            UnityPlayer.UnitySendMessage("SpeechToText", "onBeginningOfSpeech", "");
        }
        @Override
        public void onRmsChanged(float rmsdB) {
            //UnityPlayer.UnitySendMessage("SpeechToText", "onRmsChanged", "" + rmsdB);
        }
        @Override
        public void onBufferReceived(byte[] buffer) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "onBufferReceived: " + buffer.length);
        }
        @Override
        public void onEndOfSpeech() {
            UnityPlayer.UnitySendMessage("SpeechToText", "onEndOfSpeech", "");
        }
        @Override
        public void onError(int error) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onError", "" + error);
        }
        @Override
        public void onResults(Bundle results) {
            try
            {
                ArrayList<String> text = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                UnityPlayer.UnitySendMessage("SpeechToText", "onResults", text.get(0));
            }
            catch (NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onResults " + e.getMessage());
            }

        }
        @Override
        public void onPartialResults(Bundle partialResults) {
            try
            {
                ArrayList<String> text = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                UnityPlayer.UnitySendMessage("SpeechToText", "onPartialResults", text.get(0));
            }
            catch(NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onPartialResults " + e.getMessage());
            }
            catch(IndexOutOfBoundsException i)
            {
                Log.i("Unity", "IndexOutOfBoundsException from a onResults " + i.getMessage());
            }
        }
        @Override
        public void onEvent(int eventType, Bundle params) {

            UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "onEvent");
        }
    };

    ////
    public  void OnStartSpeak(String valueText)
    {
        tts.speak(valueText, TextToSpeech.QUEUE_FLUSH, null, valueText);
    }
    public void OnSettingSpeak(String language, float pitch, float rate) {
        tts.setPitch(pitch);
        tts.setSpeechRate(rate);
        int result = tts.setLanguage(getLocaleFromString(language));
        UnityPlayer.UnitySendMessage("TextToSpeech", "onSettingResult", "" + result);
    }
    public void OnStopSpeak()
    {
        tts.stop();
    }

    TextToSpeech.OnInitListener initListener = new TextToSpeech.OnInitListener()
    {
        @Override
        public void onInit(int status) {
            if (status == TextToSpeech.SUCCESS)
            {
                OnSettingSpeak(Locale.US.toString(), 1.0f, 1.0f);
                tts.setOnUtteranceProgressListener(utteranceProgressListener);
            }
        }
    };

    UtteranceProgressListener utteranceProgressListener=new UtteranceProgressListener() {
        @Override
        public void onStart(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onStart", utteranceId);
        }
        @Override
        public void onError(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onError", utteranceId);
        }
        @Override
        public void onDone(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onDone", utteranceId);
        }
    };

    /**
     * Convert a string based locale into a Locale Object.
     * Assumes the string has form "{language}_{country}_{variant}".
     * Examples: "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "fr_MAC"
     *
     * @param localeString The String
     * @return the Locale
     */
    public static Locale getLocaleFromString(String localeString)
    {
        if (localeString == null)
        {
            return null;
        }
        localeString = localeString.trim();
        if (localeString.equalsIgnoreCase("default"))
        {
            return Locale.getDefault();
        }

        // Extract language
        int languageIndex = localeString.indexOf('_');
        String language;
        if (languageIndex == -1)
        {
            // No further "_" so is "{language}" only
            return new Locale(localeString, "");
        }
        else
        {
            language = localeString.substring(0, languageIndex);
        }

        // Extract country
        int countryIndex = localeString.indexOf('_', languageIndex + 1);
        String country;
        if (countryIndex == -1)
        {
            // No further "_" so is "{language}_{country}"
            country = localeString.substring(languageIndex+1);
            return new Locale(language, country);
        }
        else
        {
            // Assume all remaining is the variant so is "{language}_{country}_{variant}"
            country = localeString.substring(languageIndex+1, countryIndex);
            String variant = localeString.substring(countryIndex+1);
            return new Locale(language, country, variant);
        }
    }
}

I had NullPointerException and IndexOutOfBoundsException to avoid those problems for now. And also had intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); in the intent started without popup.

So exception are catch and application doesn't crashed !

I need to re-build the plugin also

ghKoty commented 1 year ago

Android studio not working on my pc, can you give me rebuild of this plugin for Unity?

RohanJ-ui commented 1 year ago

Hi !

I had the same problem with isShowPopupAndroid = false. And for now i fixed it. I'm not on the same version as you.

  • Unity version 2021.3.10
  • Android version 8 and 9

I had to investigate this problem. And it seams when you're not using the popup, google services can't manage errors.

Thanks to the adb debugger I found multiple exception with a runtime exception --> CRASH.

Like this one:

image

So I go in the plugin with Android Studio and manage exception for now.

public class MainActivity extends UnityPlayerActivity
{
    private TextToSpeech tts;
    private SpeechRecognizer speech;
    private Intent intent;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tts = new TextToSpeech(this, initListener);

        speech = SpeechRecognizer.createSpeechRecognizer(this);
        speech.setRecognitionListener(recognitionListener);
    }
    @Override
    public void onDestroy() {
        // Don't forget to shutdown tts!
        if (tts != null) {
            tts.stop();
            tts.shutdown();
        }
        if (speech != null) {
            speech.destroy();
        }
        super.onDestroy();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && null != data) {
            try
            {
                ArrayList<String> text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                UnityPlayer.UnitySendMessage("SpeechToText", "onResults", text.get(0));
            }
            catch(NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onPartialResults " + e.getMessage());
            }
        }
    }

    // speech to text
    public void OnStartRecording() {
        intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Bridge.languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        //intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 2000);
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 2000);
        //intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 2000);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
        //onLog("test");

        /*Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE, languageSpeech);
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, Long.valueOf(5000));
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, Long.valueOf(3000));
        intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, Long.valueOf(3000));
        if (!prompt.equals("")) intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
        UnityPlayer.currentActivity.startActivityForResult(intent, RESULT_SPEECH);*/

        this.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                speech.startListening(intent);
            }
        });
        UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "CallStart, Language:" + Bridge.languageSpeech);
    }
    public void OnStopRecording() {
        this.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                speech.stopListening();
            }
        });
        UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "CallStop");
    }

    public void onLog(String m)
    {
        UnityPlayer.UnitySendMessage("SpeechToText", "onLog", m.toString());
    }

    RecognitionListener recognitionListener = new RecognitionListener() {

        @Override
        public void onReadyForSpeech(Bundle params) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onReadyForSpeech", params.toString());
        }
        @Override
        public void onBeginningOfSpeech() {
            UnityPlayer.UnitySendMessage("SpeechToText", "onBeginningOfSpeech", "");
        }
        @Override
        public void onRmsChanged(float rmsdB) {
            //UnityPlayer.UnitySendMessage("SpeechToText", "onRmsChanged", "" + rmsdB);
        }
        @Override
        public void onBufferReceived(byte[] buffer) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "onBufferReceived: " + buffer.length);
        }
        @Override
        public void onEndOfSpeech() {
            UnityPlayer.UnitySendMessage("SpeechToText", "onEndOfSpeech", "");
        }
        @Override
        public void onError(int error) {
            UnityPlayer.UnitySendMessage("SpeechToText", "onError", "" + error);
        }
        @Override
        public void onResults(Bundle results) {
            try
            {
                ArrayList<String> text = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                UnityPlayer.UnitySendMessage("SpeechToText", "onResults", text.get(0));
            }
            catch (NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onResults " + e.getMessage());
            }

        }
        @Override
        public void onPartialResults(Bundle partialResults) {
            try
            {
                ArrayList<String> text = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                UnityPlayer.UnitySendMessage("SpeechToText", "onPartialResults", text.get(0));
            }
            catch(NullPointerException e)
            {
                Log.i("Unity", "NullPointerException from a onPartialResults " + e.getMessage());
            }
            catch(IndexOutOfBoundsException i)
            {
                Log.i("Unity", "IndexOutOfBoundsException from a onResults " + i.getMessage());
            }
        }
        @Override
        public void onEvent(int eventType, Bundle params) {

            UnityPlayer.UnitySendMessage("SpeechToText", "onMessage", "onEvent");
        }
    };

    ////
    public  void OnStartSpeak(String valueText)
    {
        tts.speak(valueText, TextToSpeech.QUEUE_FLUSH, null, valueText);
    }
    public void OnSettingSpeak(String language, float pitch, float rate) {
        tts.setPitch(pitch);
        tts.setSpeechRate(rate);
        int result = tts.setLanguage(getLocaleFromString(language));
        UnityPlayer.UnitySendMessage("TextToSpeech", "onSettingResult", "" + result);
    }
    public void OnStopSpeak()
    {
        tts.stop();
    }

    TextToSpeech.OnInitListener initListener = new TextToSpeech.OnInitListener()
    {
        @Override
        public void onInit(int status) {
            if (status == TextToSpeech.SUCCESS)
            {
                OnSettingSpeak(Locale.US.toString(), 1.0f, 1.0f);
                tts.setOnUtteranceProgressListener(utteranceProgressListener);
            }
        }
    };

    UtteranceProgressListener utteranceProgressListener=new UtteranceProgressListener() {
        @Override
        public void onStart(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onStart", utteranceId);
        }
        @Override
        public void onError(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onError", utteranceId);
        }
        @Override
        public void onDone(String utteranceId) {
            UnityPlayer.UnitySendMessage("TextToSpeech", "onDone", utteranceId);
        }
    };

    /**
     * Convert a string based locale into a Locale Object.
     * Assumes the string has form "{language}_{country}_{variant}".
     * Examples: "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "fr_MAC"
     *
     * @param localeString The String
     * @return the Locale
     */
    public static Locale getLocaleFromString(String localeString)
    {
        if (localeString == null)
        {
            return null;
        }
        localeString = localeString.trim();
        if (localeString.equalsIgnoreCase("default"))
        {
            return Locale.getDefault();
        }

        // Extract language
        int languageIndex = localeString.indexOf('_');
        String language;
        if (languageIndex == -1)
        {
            // No further "_" so is "{language}" only
            return new Locale(localeString, "");
        }
        else
        {
            language = localeString.substring(0, languageIndex);
        }

        // Extract country
        int countryIndex = localeString.indexOf('_', languageIndex + 1);
        String country;
        if (countryIndex == -1)
        {
            // No further "_" so is "{language}_{country}"
            country = localeString.substring(languageIndex+1);
            return new Locale(language, country);
        }
        else
        {
            // Assume all remaining is the variant so is "{language}_{country}_{variant}"
            country = localeString.substring(languageIndex+1, countryIndex);
            String variant = localeString.substring(countryIndex+1);
            return new Locale(language, country, variant);
        }
    }
}

I had NullPointerException and IndexOutOfBoundsException to avoid those problems for now. And also had intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); in the intent started without popup.

So exception are catch and application doesn't crashed !

I need to re-build the plugin also

hey buddy, I have done the change you mentioned, in the android project. Could you please tell shortly how do I rebuild the plugin in android studio.? I mean I tried rebuilding the project in android studio but I believe i need a .jar file for the unity plugin right?

Uralstech commented 10 months ago

hey buddy, I have done the change you mentioned, in the android project. Could you please tell shortly how do I rebuild the plugin in android studio.? I mean I tried rebuilding the project in android studio but I believe i need a .jar file for the unity plugin right?

I know I'm a bit late to this issue, but this worked for me:

Uralstech commented 10 months ago

Also, this issue might have something to do with this: [Bug] Null results with Voice Recognition Service... In my case, I am getting the speech results in onPartialResults but null in onResults.

alexgesti commented 8 months ago

hey buddy, I have done the change you mentioned, in the android project. Could you please tell shortly how do I rebuild the plugin in android studio.? I mean I tried rebuilding the project in android studio but I believe i need a .jar file for the unity plugin right?

I know I'm a bit late to this issue, but this worked for me:

  • Rebuild the project as you did.
  • Go to the location of the .aar file (SpeechToText\app\build\outputs\aar) created by Android Studio.
  • Create a duplicate of the .aar file.
  • Rename it with the .zip extension.
  • Extract the ZIP file.
  • Rename the classes.jar file inside the extracted folder to SpeechToTextPlugin.jar.
  • Import it into Unity.

Do you have the .jar? I really never use Android Studio and I don't know how it works....

Uralstech commented 7 months ago

Do you have the .jar? I really never use Android Studio and I don't know how it works....

You can download the .jar file here: https://github.com/Uralstech/speech-and-text-unity-ios-android/releases/tag/1.0.0 I have made a few other changes - if you're interested you can check the commits: https://github.com/j1mmyto9/speech-and-text-unity-ios-android/compare/main...Uralstech:speech-and-text-unity-ios-android:main

JebarajDaniel commented 4 months ago

Hi @Uralstech, When i use SpeechToTextPlugin.jar from main source, app get crashes when speak stop in onResults. because its has null value, when i try to use jar from link you share its not crashing now. but its does not print result because its null. when enable show android popup - when start recording - can see the result when user stop speaking but not using speech popup android, cant get the same result when user stop speaking. how to solve this issue.

Uralstech commented 4 months ago

Hi @Uralstech, When i use SpeechToTextPlugin.jar from main source, app get crashes when speak stop in onResults. because its has null value, when i try to use jar from link you share its not crashing now. but its does not print result because its null. when enable show android popup - when start recording - can see the result when user stop speaking but not using speech popup android, cant get the same result when user stop speaking. how to solve this issue.

Hi! Thanks for checking out my fork, @JebarajDaniel!

You will have to keep track of the partial results in a variable, like this:

private string _partialResults;
private void Start()
{
    // STT & TTS setup code goes here.

    SpeechToText.Instance.onResultCallback += (results) =>
    {
        if (string.IsNullOrEmpty(results))
            Debug.Log(_partialResults);
        else
            Debug.Log(results);

        _partialResults = string.Empty;
    };

    SpeechToText.Instance.onPartialResultsCallback += (partialResults) => _partialResults = partialResults;
}
JebarajDaniel commented 3 months ago

hi @Uralstech thanks for reaching out. Do you have the .jar file with this changes? I really never use Android Studio before and I don't know how it works.... if you help me - how to make jar file with this changes - its very great to learn new thing from now on. thanks

Uralstech commented 3 months ago

Do you have the .jar? I really never use Android Studio and I don't know how it works....

You can download the .jar file here: https://github.com/Uralstech/speech-and-text-unity-ios-android/releases/tag/1.0.0 I have made a few other changes - if you're interested you can check the commits: main...Uralstech:speech-and-text-unity-ios-android:main

You can find the .jar file here. If you want to build the latest code, follow these steps:

  1. Clone the repository: Clone or download the repository.

  2. Open in Android Studio: Open the project in Android Studio to build the native plugin.

  3. Modify as Needed: You can make changes to the Java source files as per your project's requirements.

  4. Build the Plugin: Use Android Studio's build tools to compile and generate a new .aab file.

  5. Integrate with Unity: Replace the existing plugin in your Unity project with the newly built version, if necessary.