获取音频 mp3 文件列表并在 Unity 3d 中播放它们

Get list of audio mp3 files and play them in Unity 3d

我正在尝试构建一个音频播放器,它可以从外部源获取 mp3 文件以及播放、停止、下一首和上一首按钮的逻辑,但我还不知道该怎么做。

http://localhost:8383/Unity3d/Audio/BT - Paul Van Dyk - Namistai.mp3" http://localhost:8383/Unity3d/Audio/Stevie Wonder - Skeletons.mp3

然后使用 C#,我为 URI 打开一个 UnityWebRequest,将它保存到一个 AudioClip 变量中,然后保存到一个 AudioSource 变量中,但是我发现使用长度 property/saving 到一些某种音频剪辑数组。

下面是我的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;
using UnityEngine.UI;

[RequireComponent(typeof(AudioSource))]
public class AudioManager : MonoBehaviour
{
    public AudioClip[] musicClips;
    private int currentTrack;
    private AudioSource source;
    public List<string> externalAudio;

IEnumerator GetAudioClip()
{
    externalAudio.Add("http://localhost:8383/Unity3d/Audio/Stevie%20Wonder%20-%20Skeletons.mp3");
    externalAudio.Add("http://localhost:8383/Unity3d/Audio/York%20-%20On%20The%20Beach.mp3");
    externalAudio.Add("http://localhost:8383/Unity3d/Audio/BT%20-%20Paul%20Van%20Dyk%20-%20Namistai.mp3");
    externalAudio.Add("http://localhost:8383/Unity3d/Audio/S.O.S.%20BAND-JUST%20BE%20GOOD%20TO%20ME%20(SINGLE).mp3");
    externalAudio.Add("http://localhost:8383/Unity3d/Audio/Michael%20Jackson%20-%20Beat%20It%20(Official%20Video).mp3");
    
    Debug.Log("Hello World");
    Debug.Log(UnityWebRequestMultimedia.GetAudioClip("http://localhost:8383/Unity3d/Audio/Stevie%20Wonder%20-%20Skeletons.mp3", AudioType.MPEG));

    foreach (var audioClip in externalAudio)
    {
        Debug.Log(audioClip);

        using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(audioClip, AudioType.MPEG))
        {
            UnityWebRequest request = new UnityWebRequest();

            yield return www.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError)
            {
                Debug.Log(www.error);
            }
            else
            {
                musicClips = DownloadHandlerAudioClip.GetContent(www);
                source.clip = musicClips;
                Debug.Log("Audio is playing.");
            }
        }
    }
}

// Start is called before the first frame update
void Start()
{
    source = GetComponent<AudioSource>();

    //PLAY MUSIC
    PlayMusic();        
}

public void PlayMusic()
{
    if (source.isPlaying)
    {
        return;
    }
    GetAudioClip();

    currentTrack--;
    if (currentTrack < 0)
    {
        currentTrack = 0;
    }
    StartCoroutine(WaitForMusicEnd());
}

IEnumerator WaitForMusicEnd()
{
    while (source.isPlaying)
    {
        yield return null;
    }
    NextTitle();
}

public void NextTitle()
{
    source.Stop();
    currentTrack++;

    if (currentTrack > musicClips.Length - 1)
    {
        currentTrack = 0;
    }
    source.clip = musicClips[currentTrack];
    source.Play();

    //Show title

    StartCoroutine("WaitForMusicEnd");
}

public void PreviousTitle()
{
    source.Stop();
    currentTrack--;

    if (currentTrack < 0)
    {
        currentTrack = musicClips.Length - 1;
    }
    source.clip = musicClips[currentTrack];
    source.Play();

    //Show title

    StartCoroutine("WaitForMusicEnd");
}

public void StopMusic()
{
    StopCoroutine("WaitForMusicEnd");
    source.Stop();
}
}

如何将外部音频保存到 AudioClip 列表中,然后在 play/stop 函数逻辑中使用它们?

所以这有点宽泛,但这是我可能会做的。

  • 如前所述,让您的服务器提供下载 URL 列表。这样您就不必将它们硬编码到您的客户端中。

  • 对于上一点,您要做的第一件事是一个普通的 GET 请求来接收该 URL 列表,例如简单地用 \n 分隔(换行符)

    因此您收到的第一条字符串消息可能类似于

    http://localhost:8383/Moonbeam%20Challenge/Audio/Stevie%20Wonder%20-%20Skeletons.mp3   
    http://localhost:8383/Moonbeam%20Challenge/Audio/York%20-%20On%20The%20Beach.mp3
    http://localhost:8383/Moonbeam%20Challenge/Audio/BT%20-%20Paul%20Van%20Dyk%20-%20Namistai.mp3
    http://localhost:8383/Moonbeam%20Challenge/Audio/S.O.S.%20BAND-JUST%20BE%20GOOD%20TO%20ME%20(SINGLE).mp3
    http://localhost:8383/Moonbeam%20Challenge/Audio/Michael%20Jackson%20-%20Beat%20It%20(Official%20Video).mp3
    
  • 收到的这份清单将您 Split 放入各个 URL

    var urls = urlList.Split('\n');
    
  • 对于这些,您可以开始您的个人 GetAudioClip 请求。

  • 您作为元素存储在 array/a 列表中的结果。您没有分配整个 musicClips。我宁愿使用

     List<AudioClip> musicClips = new List<AudioClip>();
    

    然后从您的下载中执行

     musicClips.Add(DownloadHandlerAudioClip.GetContent(www));
    
  • 您也不希望下载后立即将每个下载的剪辑分配给 AudioSource 并播放,而是等到所有剪辑都下载完毕(或至少是第一个,具体取决于根据您的需要)

所以这看起来有点像

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;
using UnityEngine.UI;

using System.Linq;

[RequireComponent(typeof(AudioSource))]
public class AudioManager : MonoBehaviour
{
    [Header("References")]
    [SerializeField]
    private AudioSource source;

    [Header("Settings")] 
    [SerializeField] private string baseURL;
    [SerializeField] private string[] externalAudio;
    [SerializeField] private bool autoStartPlayAfterDownloads = true;

    [Header("Debugging")] 
    [SerializeField] private List<AudioClip> musicClips = new List<AudioClip>();
    [SerializeField] private int currentTrack;
    [SerializeField] private bool isInitialized;

    private Coroutine currentPlayTrack;

    // Yes, if you make Start return IEnumerator then Units
    // automatically runs it as a Coroutine
    private IEnumerator Start()
    {
        // block input from the outside until this controller is finished with the downloads
        isInitialized = false;

        if (!source) source = GetComponent<AudioSource>();

        // Here either use the approach with first receiving a list from the server
        yield return GetAudioClipsInfo();

        // OR if you already have the clip urls assigned via the Inspector use one of
        yield return GetAudioClipsSequencial();
        // OR
        yield return GetAudioClipsParallel();

        // allow to do things from this point on
        isInitialized = true;

        if(autoStartPlayAfterDownloads) PlayFirstTitle();
    }

    // This is the routine that downloads the URL list from the server
    // then it starts the individual downloads
    private IEnumerator GetAudioClipsInfo()
    {
        // Make the first request for receiving the list of URLs from the server
        // If not using this but already assigning the urls
        // via Inspector then simply directly use the "GetAudioClips" below
        using (var request = UnityWebRequest.Get(baseURL))
        {
            yield return request.SendWebRequest();

            switch (request.result)
            {
                case UnityWebRequest.Result.ConnectionError:
                case UnityWebRequest.Result.DataProcessingError:
                case UnityWebRequest.Result.ProtocolError:
                    Debug.LogError($"Could not get list of clips! Error: {request.error}", this);

                    yield break;
            }

            var urlList = request.downloadHandler.text;

            externalAudio = urlList.Split('\n');

            // Here you can either go for the sequencial downloads
            yield return GetAudioClipsSequencial();

            // OR run them all parallel
            yield return GetAudioClipsParallel();
        }
    }

    // This version starts one download at a time, waits until it is done
    // then starts the next 
    private IEnumerator GetAudioClipsSequencial()
    {
        musicClips.Clear();

        foreach (var url in externalAudio)
        {
            Debug.Log(url);

            using (var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.MPEG))
            {
                yield return www.SendWebRequest();

                switch (www.result)
                {
                    case UnityWebRequest.Result.ConnectionError:
                    case UnityWebRequest.Result.DataProcessingError:
                    case UnityWebRequest.Result.ProtocolError:
                        Debug.LogError($"Could not get clip from \"{url}\"! Error: {www.error}", this);

                        continue;
                }

                musicClips.Add(DownloadHandlerAudioClip.GetContent(www));
            }
        }
    }

    // This version starts all downloads at once and waits until they are all done
    // probably faster than the sequencial version
    private IEnumerator GetAudioClipsParallel()
    {
        musicClips.Clear();
        var requests = new List<UnityWebRequest>();

        foreach (var url in externalAudio)
        {
            Debug.Log(url);

            var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.MPEG);

            // Start the request without waiting
            www.SendWebRequest();
            requests.Add(www);
        }

        // Wait for all requests to finish
        yield return new WaitWhile(() => requests.Any(r => !r.isDone));

        // Now examine and use all results
        foreach (var www in requests)
        {
            switch (www.result)
            {
                case UnityWebRequest.Result.ConnectionError:
                case UnityWebRequest.Result.DataProcessingError:
                case UnityWebRequest.Result.ProtocolError:
                    Debug.LogError($"Could not get clip from \"{www.url}\"! Error: {www.error}", this);

                    continue;
            }

            musicClips.Add(DownloadHandlerAudioClip.GetContent(www));

            www.Dispose();
        }
    }

    public void PlayFirstTitle()
    {
        if (!isInitialized) return;

        if (source.isPlaying) return;

        if (currentPlayTrack != null)
        {
            StopCoroutine(currentPlayTrack);
        }

        currentPlayTrack = StartCoroutine(PlayTrack(0));
    }

    private IEnumerator PlayTrack(int index)
    {
        // Make sure the index is within the given clips range
        index = Mathf.Clamp(index, 0, musicClips.Count);

        // update the current track to make next and previous work
        currentTrack = index;

        // get clip by index
        var clip = musicClips[currentTrack];

        // Assign and play
        source.clip = clip;
        source.Play();

        // wait for clip end
        while (source.isPlaying)
        {
            yield return null;
        }

        NextTitle();
    }

    public void NextTitle()
    {
        if (!isInitialized) return;

        if (currentPlayTrack != null)
        {
            StopCoroutine(currentPlayTrack);
        }

        source.Stop();
        currentTrack = (currentTrack + 1) % musicClips.Count;

        currentPlayTrack = StartCoroutine(PlayTrack(currentTrack));
    }

    public void PreviousTitle()
    {
        if (!isInitialized) return;

        if (currentPlayTrack != null)
        {
            StopCoroutine(currentPlayTrack);
        }

        source.Stop();
        currentTrack--;

        if (currentTrack < 0)
        {
            currentTrack = musicClips.Count - 1;
        }

        currentPlayTrack = StartCoroutine(PlayTrack(currentTrack));
    }

    public void StopMusic()
    {
        if (!isInitialized) return;

        if (currentPlayTrack != null)
        {
            StopCoroutine(currentPlayTrack);
        }

        source.Stop();
    }
}