// ----------------------------------------------------------------------------
//
// Photon Voice - Copyright (C) 2018 Exit Games GmbH
//
//
// Component that should be attached to a networked PUN prefab that has
// PhotonView. It will bind remote Recorder with local Speaker of the same
// networked prefab. This component makes automatic voice stream routing easy
// for players' characters/avatars.
//
// developer@photonengine.com
// ----------------------------------------------------------------------------
namespace Photon.Voice.PUN
{
using Pun;
using UnityEngine;
using Unity;
///
/// Component that should be attached to a networked PUN prefab that has .
/// It will bind remote with local of the same networked prefab.
/// This component makes automatic voice stream routing easy for players' characters/avatars.
///
[AddComponentMenu("Photon Voice/Photon Voice View")]
[RequireComponent(typeof(PhotonView))]
[HelpURL("https://doc.photonengine.com/en-us/voice/v2/getting-started/voice-for-pun")]
public class PhotonVoiceView : VoiceComponent
{
#region Private Fields
private PhotonView photonView;
[SerializeField]
private Recorder recorderInUse;
[SerializeField]
private Speaker speakerInUse;
private bool onEnableCalledOnce;
#endregion
#region Public Fields
/// If true, a Recorder component will be added to the same GameObject if not found already.
public bool AutoCreateRecorderIfNotFound;
/// If true, PhotonVoiceNetwork.PrimaryRecorder will be used by this PhotonVoiceView
public bool UsePrimaryRecorder;
/// If true, a Speaker component will be setup to be used for the DebugEcho mode
public bool SetupDebugSpeaker;
#endregion
#region Properties
/// The Recorder component currently used by this PhotonVoiceView
public Recorder RecorderInUse
{
get
{
return this.recorderInUse;
}
set
{
if (value != this.recorderInUse)
{
this.recorderInUse = value;
this.IsRecorder = false;
}
if (this.RequiresRecorder)
{
this.SetupRecorderInUse();
}
else if (this.IsPhotonViewReady)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("No need to set Recorder as the PhotonView does not belong to local player");
}
}
}
}
/// The Speaker component currently used by this PhotonVoiceView
public Speaker SpeakerInUse
{
get
{
return this.speakerInUse;
}
set
{
if (this.speakerInUse != value)
{
this.speakerInUse = value;
this.IsSpeaker = false;
}
if (this.RequiresSpeaker)
{
this.SetupSpeakerInUse();
}
else if (this.IsPhotonViewReady)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Speaker not set because the PhotonView does not belong to a remote player or SetupDebugSpeaker is disabled");
}
}
}
}
/// If true, this PhotonVoiceView is setup and ready to be used
public bool IsSetup
{
get { return this.IsPhotonViewReady && (!this.RequiresRecorder || this.IsRecorder) && (!this.RequiresSpeaker || this.IsSpeaker); }
}
/// If true, this PhotonVoiceView has a Speaker setup for playback of received audio frames from remote audio source
public bool IsSpeaker { get; private set; }
/// If true, this PhotonVoiceView has a Speaker that is currently playing received audio frames from remote audio source
public bool IsSpeaking
{
get { return this.IsSpeaker && this.SpeakerInUse.IsPlaying; }
}
/// If true, this PhotonVoiceView has a Recorder setup for transmission of audio stream from local audio source
public bool IsRecorder { get; private set; }
/// If true, this PhotonVoiceView has a Recorder that is currently transmitting audio stream from local audio source
public bool IsRecording
{
get { return this.IsRecorder && this.RecorderInUse.IsCurrentlyTransmitting; }
}
/// If true, the SpeakerInUse is linked to the remote voice stream
public bool IsSpeakerLinked
{
get { return this.IsSpeaker && this.SpeakerInUse.IsLinked; }
}
/// If true, the PhotonView attached to the same GameObject has a valid ViewID > 0
public bool IsPhotonViewReady
{
get { return this.photonView && this.photonView != null && this.photonView.ViewID > 0; }
}
internal bool RequiresSpeaker
{
get { return this.SetupDebugSpeaker || this.IsPhotonViewReady && !this.photonView.IsMine; }
}
internal bool RequiresRecorder
{
get { return this.IsPhotonViewReady && this.photonView.IsMine; }
}
#endregion
#region Private Methods
protected override void Awake()
{
base.Awake();
this.photonView = this.GetComponent();
this.Init();
}
private void OnEnable()
{
if (this.onEnableCalledOnce)
{
this.Init();
}
else
{
this.onEnableCalledOnce = true;
}
}
private void Start()
{
this.Init();
}
private void CheckLateLinking()
{
if (PhotonVoiceNetwork.Instance.Client.InRoom)
{
if (this.IsSpeaker)
{
if (!this.IsSpeakerLinked)
{
PhotonVoiceNetwork.Instance.CheckLateLinking(this.SpeakerInUse, this.photonView.ViewID);
}
else if (this.Logger.IsDebugEnabled)
{
this.Logger.LogDebug("Speaker already linked");
}
}
else if (this.Logger.IsDebugEnabled)
{
this.Logger.LogDebug("PhotonVoiceView does not have a Speaker and may not need late linking check");
}
}
else if (this.Logger.IsDebugEnabled)
{
this.Logger.LogDebug("Voice client is still not in a room, skipping late linking check");
}
}
internal void Setup()
{
if (this.IsSetup)
{
if (this.Logger.IsDebugEnabled)
{
this.Logger.LogDebug("PhotonVoiceView already setup");
}
return;
}
this.SetupRecorderInUse();
this.SetupSpeakerInUse();
}
private bool SetupRecorder()
{
if (this.recorderInUse == null) // not manually assigned by user
{
if (this.UsePrimaryRecorder)
{
if (PhotonVoiceNetwork.Instance.PrimaryRecorder != null)
{
this.recorderInUse = PhotonVoiceNetwork.Instance.PrimaryRecorder;
return this.SetupRecorder(this.recorderInUse);
}
if (this.Logger.IsErrorEnabled)
{
this.Logger.LogError("PrimaryRecorder is not set.");
}
}
Recorder[] recorders = this.GetComponentsInChildren();
if (recorders.Length > 0)
{
this.recorderInUse = recorders[0];
if (recorders.Length > 1 && this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Multiple Recorder components found attached to the GameObject or its children.");
}
return this.SetupRecorder(this.recorderInUse);
}
if (!this.AutoCreateRecorderIfNotFound)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("No Recorder found to be setup.");
}
return false;
}
this.recorderInUse = this.gameObject.AddComponent();
}
return this.SetupRecorder(this.recorderInUse);
}
private bool SetupRecorder(Recorder recorder)
{
if (recorder == null)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Cannot setup a null Recorder.");
}
return false;
}
if (!this.IsPhotonViewReady)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Recorder setup cannot be done before assigning a valid ViewID to the PhotonView attached to the same GameObject as the PhotonVoiceView.");
}
return false;
}
recorder.UserData = this.photonView.ViewID;
if (!recorder.IsInitialized)
{
this.RecorderInUse.Init(PhotonVoiceNetwork.Instance);
}
if (recorder.RequiresRestart)
{
recorder.RestartRecording();
}
return recorder.IsInitialized && recorder.UserData is int && this.photonView.ViewID == (int) recorder.UserData;
}
private bool SetupSpeaker()
{
if (this.speakerInUse == null) // not manually assigned by user
{
Speaker[] speakers = this.GetComponentsInChildren(true);
if (speakers.Length > 0)
{
this.speakerInUse = speakers[0];
if (speakers.Length > 1 && this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Multiple Speaker components found attached to the GameObject or its children. Using the first one we found.");
}
}
if (this.speakerInUse == null)
{
if (!PhotonVoiceNetwork.Instance.AutoCreateSpeakerIfNotFound)
{
return false;
}
if (PhotonVoiceNetwork.Instance.SpeakerPrefab != null)
{
GameObject go = Instantiate(PhotonVoiceNetwork.Instance.SpeakerPrefab, this.transform, false);
speakers = go.GetComponentsInChildren(true);
if (speakers.Length > 0)
{
this.speakerInUse = speakers[0];
if (speakers.Length > 1 && this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Multiple Speaker components found attached to the GameObject (PhotonVoiceNetwork.SpeakerPrefab) or its children. Using the first one we found.");
}
}
if (this.speakerInUse == null)
{
if (this.Logger.IsErrorEnabled)
{
this.Logger.LogError("SpeakerPrefab does not have a component of type Speaker in its hierarchy.");
}
Destroy(go);
return false;
}
}
else
{
this.speakerInUse = this.gameObject.AddComponent();
}
}
}
return this.SetupSpeaker(this.speakerInUse);
}
private bool SetupSpeaker(Speaker speaker)
{
if (speaker == null)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Cannot setup a null Speaker");
}
return false;
}
AudioSource audioSource = speaker.GetComponent();
if (audioSource == null)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Unexpected: no AudioSource found attached to the same GameObject as the Speaker component");
}
return false;
}
if (audioSource.mute)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("audioSource.mute is true, playback may not work properly");
}
}
if (audioSource.volume <= 0f)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("audioSource.volume is zero, playback may not work properly");
}
}
if (!audioSource.enabled)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("audioSource.enabled is false, playback may not work properly");
}
}
return true;
}
internal void SetupRecorderInUse()
{
if (this.IsRecorder)
{
if (this.Logger.IsInfoEnabled)
{
this.Logger.LogInfo("Recorder already setup");
}
return;
}
if (!this.RequiresRecorder)
{
if (this.IsPhotonViewReady)
{
if (this.Logger.IsInfoEnabled)
{
this.Logger.LogInfo("Recorder not needed");
}
}
return;
}
this.IsRecorder = this.SetupRecorder();
if (!this.IsRecorder)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Recorder not setup for PhotonVoiceView: playback may not work properly.");
}
}
else
{
if (!this.RecorderInUse.IsRecording && !this.RecorderInUse.AutoStart)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("PhotonVoiceView.RecorderInUse.AutoStart is false, don't forget to start recording manually using recorder.StartRecording() or recorder.IsRecording = true.");
}
}
if (!this.RecorderInUse.TransmitEnabled)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("PhotonVoiceView.RecorderInUse.TransmitEnabled is false, don't forget to set it to true to enable transmission.");
}
}
if (!this.RecorderInUse.isActiveAndEnabled && this.RecorderInUse.RecordOnlyWhenEnabled)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("PhotonVoiceView.RecorderInUse may not work properly as RecordOnlyWhenEnabled is set to true and recorder is disabled or attached to an inactive GameObject.");
}
}
}
}
internal void SetupSpeakerInUse()
{
if (this.IsSpeaker)
{
if (this.Logger.IsInfoEnabled)
{
this.Logger.LogInfo("Speaker already setup");
}
return;
}
if (!this.RequiresSpeaker)
{
if (this.IsPhotonViewReady)
{
if (this.Logger.IsInfoEnabled)
{
this.Logger.LogInfo("Speaker not needed");
}
}
return;
}
this.IsSpeaker = this.SetupSpeaker();
if (!this.IsSpeaker)
{
if (this.Logger.IsWarningEnabled)
{
this.Logger.LogWarning("Speaker not setup for PhotonVoiceView: voice chat will not work.");
}
}
else
{
this.CheckLateLinking();
}
}
#endregion
#region Public Methods
///
/// Initializes this PhotonVoiceView for Voice usage based on the PhotonView, Recorder and Speaker components.
///
///
/// The initialization should happen automatically.
/// Call this method explicitly if this does not succeed.
/// The initialization is a two steps operation: step one is the setup of Recorder and Speaker to be used.
/// Step two is the late-linking -if needed- of the SpeakerInUse and corresponding remote voice info -if any- via ViewID.
///
public void Init()
{
if (this.IsPhotonViewReady)
{
this.Setup();
this.CheckLateLinking();
}
else if (this.Logger.IsDebugEnabled)
{
this.Logger.LogDebug("Tried to initialize PhotonVoiceView but PhotonView does not have a valid allocated ViewID yet.");
}
}
#endregion
}
}