using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Unity.Netcode;
namespace XRMultiplayer
{
///
/// Controls the music playback in a room.
///
public class RoomMusic : NetworkBehaviour
{
///
/// Indicates whether the music should start playing automatically.
///
[SerializeField] bool m_AutoPlay;
///
/// The list of music clips to play.
///
[SerializeField, Tooltip("Format clip name to 'Song Title-Artist Name'")] AudioClip[] m_MusicClips;
///
/// The audio source to play the music.
///
[SerializeField] AudioSource m_AudioSource;
///
/// The slider to control the timeline of the current playing track.
///
[SerializeField] Slider m_TimelineSlider;
///
/// The slider to control the volume of the current playing track.
///
[SerializeField] Slider m_VolumeSlider;
///
/// The dropdown to select the current track to play.
///
[SerializeField] TMP_Dropdown m_Dropdown;
///
/// The text to display the current playing track.
///
[SerializeField] TMP_Text[] m_CurrentSongText;
///
/// The toggle to play/pause the current track.
///
[SerializeField] Toggle m_PlayPauseToggle;
///
/// The toggle to shuffle the music.
///
[SerializeField] Toggle m_ShuffleToggle;
///
/// The button to play the next track.
///
[SerializeField] Button m_NextButton;
///
/// The button to play the previous track.
///
[SerializeField] Button m_PreviousButton;
///
/// The image to display the play/pause state.
///
[SerializeField] Image m_PlayPauseImage;
///
/// The sprite to display the pause state.
///
[SerializeField] Sprite m_PauseSprite;
///
/// The sprite to display the play state.
///
[SerializeField] Sprite m_PlaySprite;
///
/// The network variable to store the current song ID.
///
readonly NetworkVariable m_CurrentSongIdNetworked = new NetworkVariable(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
///
/// The network variable to store the state of whether or not we are actively playing music.
///
readonly NetworkVariable m_IsPlayingNetworked = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
///
/// Indicates whether the music should be shuffled.
///
bool m_Shuffle;
///
/// The current song ID.
///
int currentSongId = 0;
///
void Start()
{
// Clear the dropdown options
m_Dropdown.ClearOptions();
if (m_MusicClips == null || m_MusicClips.Length == 0)
{
Utils.LogWarning("No music clips found in RoomMusic script. Please add some music clips to the RoomMusic script.");
enabled = false;
return;
}
// Add the music clip names as options to the dropdown
foreach (var c in m_MusicClips)
{
TMP_Dropdown.OptionData optionData = new TMP_Dropdown.OptionData(c.name);
m_Dropdown.options.Add(optionData);
}
// If auto play is enabled, pick a random song
if (m_AutoPlay)
{
PickRandomSong();
}
// Set the initial volume and clip for the audio source
m_AudioSource.volume = m_VolumeSlider.value;
m_AudioSource.clip = m_MusicClips[Random.Range(0, m_MusicClips.Length)];
SetupUIListeners();
// Update the song title text
UpdateSongTitleText();
}
///
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
// Set the volume of the audio source
m_AudioSource.volume = m_VolumeSlider.value;
// Add listeners for the network variable change events
m_CurrentSongIdNetworked.OnValueChanged += CurrentSongUpdated;
m_IsPlayingNetworked.OnValueChanged += OnIsPlayingChanged;
if (IsServer)
{
m_IsPlayingNetworked.Value = m_AutoPlay;
if (!m_AutoPlay)
{
m_CurrentSongIdNetworked.Value = 0;
SetClipTime(0.0f);
m_TimelineSlider.SetValueWithoutNotify(0.0f);
}
}
else
{
if (m_IsPlayingNetworked.Value)
{
SetSong(m_CurrentSongIdNetworked.Value);
GetCurrentSongPercFromServerRpc();
}
OnIsPlayingChanged(false, m_IsPlayingNetworked.Value);
}
CurrentSongUpdated(0, m_CurrentSongIdNetworked.Value);
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
OnIsPlayingChanged(false, false);
// Remove listeners for the network variable change events
m_CurrentSongIdNetworked.OnValueChanged -= CurrentSongUpdated;
m_IsPlayingNetworked.OnValueChanged -= OnIsPlayingChanged;
SetClipTime(0.0f);
m_TimelineSlider.SetValueWithoutNotify(0.0f);
if (IsServer)
{
if (m_CurrentSongIdNetworked != null)
m_CurrentSongIdNetworked.Value = 0;
if (m_IsPlayingNetworked != null)
m_IsPlayingNetworked.Value = false;
}
}
///
private void Update()
{
if (!m_IsPlayingNetworked.Value || !NetworkManager.Singleton.IsConnectedClient)
return;
// Calculate the current playback percentage
float perc = m_AudioSource.time / m_AudioSource.clip.length;
m_TimelineSlider.SetValueWithoutNotify(perc);
// If the playback is near the end, pick a new song
if (perc >= .999f)
{
PickNewSong();
}
}
///
public override void OnDestroy()
{
base.OnDestroy();
RemoveUIListeners();
}
///
/// Sets up the listeners for UI events.
///
void SetupUIListeners()
{
m_TimelineSlider.onValueChanged.AddListener(SetClipTime);
m_VolumeSlider.onValueChanged.AddListener(UpdateVolume);
m_Dropdown.onValueChanged.AddListener(PickSong);
m_PlayPauseToggle.onValueChanged.AddListener(TogglePlay);
m_ShuffleToggle.onValueChanged.AddListener(ToggleShuffle);
m_NextButton.onClick.AddListener(delegate { PickNewSong(1); });
m_PreviousButton.onClick.AddListener(delegate { PickNewSong(-1); });
}
///
/// Removes the listeners for UI events.
///
void RemoveUIListeners()
{
m_TimelineSlider.onValueChanged.RemoveListener(SetClipTime);
m_VolumeSlider.onValueChanged.RemoveListener(UpdateVolume);
m_Dropdown.onValueChanged.RemoveListener(PickSong);
m_PlayPauseToggle.onValueChanged.RemoveListener(TogglePlay);
m_ShuffleToggle.onValueChanged.RemoveListener(ToggleShuffle);
m_NextButton.onClick.RemoveListener(delegate { PickNewSong(1); });
m_PreviousButton.onClick.RemoveListener(delegate { PickNewSong(-1); });
}
///
/// Updates the volume of the audio source.
///
/// The new volume value.
void UpdateVolume(float volume)
{
m_AudioSource.volume = volume;
}
///
/// Toggles the shuffle mode.
///
/// The new toggle state.
void ToggleShuffle(bool toggle)
{
m_Shuffle = toggle;
}
///
/// Toggles the play/pause state of the audio source.
///
/// The new toggle state.
void TogglePlay(bool toggle)
{
if (IsServer)
{
m_IsPlayingNetworked.Value = toggle;
}
}
///
/// Picks a new song based on the shuffle mode and direction.
///
/// The direction to pick the new song (-1 for previous, 1 for next).
void PickNewSong(int dir = 1)
{
if (!IsServer)
return;
if (m_Shuffle)
{
PickRandomSong();
}
else
{
int nextSongId = Utils.RealMod(m_Dropdown.value + dir, m_MusicClips.Length);
PickSong(nextSongId);
}
}
///
/// Picks a random song to play.
///
void PickRandomSong()
{
int tries = 10;
int randomSongId = m_CurrentSongIdNetworked.Value;
while (tries > 0)
{
tries--;
randomSongId = Random.Range(0, m_MusicClips.Length);
if (randomSongId != m_CurrentSongIdNetworked.Value)
{
break;
}
}
PickSong(randomSongId);
}
///
/// Picks a song to play based on the selected dropdown option.
///
/// The ID of the song to play.
void PickSong(int songId)
{
m_Dropdown.value = songId;
SetClipTime(0.0f);
if (IsServer)
{
m_CurrentSongIdNetworked.Value = songId;
m_IsPlayingNetworked.Value = true;
}
}
///
/// Plays a song based on the given song ID.
///
/// The ID of the previous song.
/// The ID of the new song.
void CurrentSongUpdated(int oldSongId, int songId)
{
if (songId >= 0 && songId < m_MusicClips.Length)
{
SetSong(songId);
if (m_IsPlayingNetworked.Value)
{
SetClipTime(0.0f);
m_AudioSource.Play();
m_PlayPauseImage.sprite = m_PauseSprite;
m_PlayPauseToggle.SetIsOnWithoutNotify(true);
}
}
}
void SetSong(int songId)
{
m_AudioSource.clip = m_MusicClips[songId];
currentSongId = songId;
UpdateSongTitleText();
}
void OnIsPlayingChanged(bool oldValue, bool newValue)
{
if (newValue)
{
m_AudioSource.Play();
m_PlayPauseImage.sprite = m_PauseSprite;
}
else
{
m_AudioSource.Pause();
m_PlayPauseImage.sprite = m_PlaySprite;
}
m_PlayPauseToggle.SetIsOnWithoutNotify(newValue);
}
///
/// Sets the clip time of the audio source based on the timeline slider value.
///
/// The new value of the timeline slider.
void SetClipTime(float value)
{
if (!enabled)
return;
m_AudioSource.time = Mathf.Clamp(value, .01f, .99f) * m_AudioSource.clip.length;
}
[Rpc(SendTo.Server)]
void GetCurrentSongPercFromServerRpc(RpcParams rpcParams = default)
{
SendCurrentSongPercToClientRpc(m_AudioSource.time / m_AudioSource.clip.length, RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp));
}
[Rpc(SendTo.SpecifiedInParams)]
void SendCurrentSongPercToClientRpc(float perc, RpcParams rpcParams = default)
{
m_AudioSource.time = perc * m_AudioSource.clip.length;
m_TimelineSlider.SetValueWithoutNotify(perc / m_AudioSource.clip.length);
}
///
/// Updates the song title text.
///
void UpdateSongTitleText()
{
string songName = m_MusicClips[currentSongId == -1 ? 0 : currentSongId].name;
string[] songNameSplit = m_MusicClips[currentSongId == -1 ? 0 : currentSongId].name.Split('-');
if (songNameSplit.Length > 1)
{
songName = $"{songNameSplit[0]}\n{songNameSplit[1]}";
}
foreach (TMP_Text text in m_CurrentSongText)
{
text.text = songName;
}
}
}
}