423 lines
13 KiB
C#
423 lines
13 KiB
C#
|
|
|||
|
using JetBrains.Annotations;
|
|||
|
using UdonSharp;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.UI;
|
|||
|
using VRC.SDK3.Components;
|
|||
|
using VRC.SDKBase;
|
|||
|
using VRC.Udon;
|
|||
|
|
|||
|
namespace UdonSharp.Video
|
|||
|
{
|
|||
|
[DefaultExecutionOrder(10)]
|
|||
|
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)]
|
|||
|
[AddComponentMenu("Udon Sharp/Video/UI/Video Control Handler")]
|
|||
|
public class VideoControlHandler : UdonSharpBehaviour
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// The video player this UI instance controls and pulls info from
|
|||
|
/// </summary>
|
|||
|
[PublicAPI, NotNull]
|
|||
|
public USharpVideoPlayer targetVideoPlayer;
|
|||
|
|
|||
|
#pragma warning disable CS0649
|
|||
|
[SerializeField]
|
|||
|
VRCUrlInputField urlField;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
Text urlFieldPlaceholderText;
|
|||
|
|
|||
|
[Header("Status text")]
|
|||
|
[SerializeField]
|
|||
|
Text statusTextField;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
Text statusTextDropShadow;
|
|||
|
|
|||
|
[Header("Video progress bar")]
|
|||
|
[SerializeField]
|
|||
|
Slider progressSlider;
|
|||
|
|
|||
|
[Header("Lock button")]
|
|||
|
[SerializeField]
|
|||
|
Graphic lockGraphic;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
GameObject masterLockedIcon, masterUnlockedIcon;
|
|||
|
|
|||
|
[Header("Info panel fields")]
|
|||
|
[SerializeField]
|
|||
|
Text masterField;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
Text ownerField;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
InputField currentURLField, previousURLField;
|
|||
|
|
|||
|
[Header("Play/Pause/Stop buttons")]
|
|||
|
[SerializeField]
|
|||
|
GameObject pauseStopObject;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
GameObject playObject;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
GameObject pauseIcon, stopIcon;
|
|||
|
|
|||
|
[Header("Loop button")]
|
|||
|
[SerializeField]
|
|||
|
Graphic loopButtonBackground;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
Graphic loopButtonIcon;
|
|||
|
|
|||
|
[Header("Video/Stream controls")]
|
|||
|
[SerializeField]
|
|||
|
SyncModeController syncController;
|
|||
|
|
|||
|
[Header("Volume")]
|
|||
|
[SerializeField]
|
|||
|
VolumeController volumeController;
|
|||
|
|
|||
|
[Header("Style Colors")]
|
|||
|
public Color redGraphicColor = new Color(0.632f, 0.19f, 0.19f);
|
|||
|
public Color whiteGraphicColor = new Color(0.9433f, 0.9433f, 0.9433f);
|
|||
|
public Color buttonBackgroundColor = new Color(1f, 1f, 1f, 1f);
|
|||
|
public Color buttonActivatedColor = new Color(1f, 1f, 1f, 1f);
|
|||
|
public Color iconInvertedColor = new Color(1f, 1f, 1f, 1f);
|
|||
|
#pragma warning restore CS0649
|
|||
|
|
|||
|
private void OnEnable()
|
|||
|
{
|
|||
|
targetVideoPlayer.RegisterControlHandler(this);
|
|||
|
UpdateMaster();
|
|||
|
UpdateVideoOwner();
|
|||
|
|
|||
|
if (volumeController) volumeController.SetControlHandler(this);
|
|||
|
}
|
|||
|
|
|||
|
private void OnDisable()
|
|||
|
{
|
|||
|
targetVideoPlayer.UnregisterControlHandler(this);
|
|||
|
}
|
|||
|
|
|||
|
// Only allow the master to own this so we can check master by checking this object's owner
|
|||
|
public override bool OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
private void Update()
|
|||
|
{
|
|||
|
RunUIUpdate();
|
|||
|
}
|
|||
|
|
|||
|
public override void OnPlayerLeft(VRCPlayerApi player)
|
|||
|
{
|
|||
|
UpdateMaster();
|
|||
|
}
|
|||
|
|
|||
|
public void OnVideoPlayerOwnerTransferred()
|
|||
|
{
|
|||
|
UpdateVideoOwner();
|
|||
|
}
|
|||
|
|
|||
|
void UpdateMaster()
|
|||
|
{
|
|||
|
#if !UNITY_EDITOR
|
|||
|
// We know the owner of this will always be the master so just get the owner and update the name
|
|||
|
if (masterField)
|
|||
|
{
|
|||
|
VRCPlayerApi owner = Networking.GetOwner(gameObject);
|
|||
|
if (Utilities.IsValid(owner))
|
|||
|
masterField.text = Networking.GetOwner(gameObject).displayName;
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
void UpdateVideoOwner()
|
|||
|
{
|
|||
|
#if !UNITY_EDITOR
|
|||
|
if (ownerField)
|
|||
|
ownerField.text = Networking.GetOwner(targetVideoPlayer.gameObject).displayName;
|
|||
|
#endif
|
|||
|
|
|||
|
SetLocked(targetVideoPlayer.IsLocked());
|
|||
|
}
|
|||
|
|
|||
|
[PublicAPI]
|
|||
|
public void SetControlledVideoPlayer(USharpVideoPlayer newPlayer)
|
|||
|
{
|
|||
|
if (newPlayer == targetVideoPlayer)
|
|||
|
return;
|
|||
|
|
|||
|
targetVideoPlayer.UnregisterControlHandler(this);
|
|||
|
targetVideoPlayer = newPlayer;
|
|||
|
targetVideoPlayer.RegisterControlHandler(this);
|
|||
|
UpdateVideoOwner();
|
|||
|
|
|||
|
SetStatusText("");
|
|||
|
_draggingSlider = false;
|
|||
|
}
|
|||
|
|
|||
|
string _currentStatusText = "";
|
|||
|
|
|||
|
public void SetStatusText(string newStatus)
|
|||
|
{
|
|||
|
_currentStatusText = newStatus;
|
|||
|
if (statusTextField) statusTextField.text = _currentStatusText;
|
|||
|
if (statusTextDropShadow) statusTextDropShadow.text = _currentStatusText;
|
|||
|
_lastTime = int.MaxValue;
|
|||
|
}
|
|||
|
|
|||
|
public void SetLocked(bool locked)
|
|||
|
{
|
|||
|
if (locked)
|
|||
|
{
|
|||
|
if (masterLockedIcon) masterLockedIcon.SetActive(true);
|
|||
|
if (masterUnlockedIcon) masterUnlockedIcon.SetActive(false);
|
|||
|
|
|||
|
if (Networking.IsOwner(targetVideoPlayer.gameObject) || targetVideoPlayer.CanControlVideoPlayer())
|
|||
|
{
|
|||
|
if (lockGraphic) lockGraphic.color = whiteGraphicColor;
|
|||
|
if (urlFieldPlaceholderText) urlFieldPlaceholderText.text = "Enter Video URL...";
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (lockGraphic) lockGraphic.color = redGraphicColor;
|
|||
|
if (urlFieldPlaceholderText) urlFieldPlaceholderText.text = $"Only the master {Networking.GetOwner(targetVideoPlayer.gameObject).displayName} may add URLs";
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (masterLockedIcon) masterLockedIcon.SetActive(false);
|
|||
|
if (masterUnlockedIcon) masterUnlockedIcon.SetActive(true);
|
|||
|
if (lockGraphic) lockGraphic.color = whiteGraphicColor;
|
|||
|
if (urlFieldPlaceholderText) urlFieldPlaceholderText.text = "Enter Video URL... (anyone)";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void SetPaused(bool paused)
|
|||
|
{
|
|||
|
bool videoMode = targetVideoPlayer.IsInVideoMode();
|
|||
|
|
|||
|
if (pauseIcon) pauseIcon.SetActive(videoMode);
|
|||
|
if (stopIcon) stopIcon.SetActive(!videoMode);
|
|||
|
|
|||
|
if (playObject) playObject.SetActive(paused);
|
|||
|
if (pauseStopObject) pauseStopObject.SetActive(!paused);
|
|||
|
}
|
|||
|
|
|||
|
public void SetLooping(bool looping)
|
|||
|
{
|
|||
|
if (looping)
|
|||
|
{
|
|||
|
if (loopButtonBackground) loopButtonBackground.color = buttonActivatedColor;
|
|||
|
if (loopButtonIcon) loopButtonIcon.color = iconInvertedColor;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (loopButtonBackground) loopButtonBackground.color = buttonBackgroundColor;
|
|||
|
if (loopButtonIcon) loopButtonIcon.color = whiteGraphicColor;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void SetVolume(float volume)
|
|||
|
{
|
|||
|
if (volumeController) volumeController.SetVolume(volume);
|
|||
|
}
|
|||
|
|
|||
|
public void SetMuted(bool muted)
|
|||
|
{
|
|||
|
if (volumeController) volumeController.SetMuted(muted);
|
|||
|
}
|
|||
|
|
|||
|
public void OnVolumeSliderChange(float volume)
|
|||
|
{
|
|||
|
targetVideoPlayer.SetVolume(volume);
|
|||
|
}
|
|||
|
|
|||
|
public void OnMutePress(bool muted)
|
|||
|
{
|
|||
|
targetVideoPlayer.SetMuted(muted);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a URL to the history display so people can copy it
|
|||
|
/// </summary>
|
|||
|
/// <param name="url"></param>
|
|||
|
public void AddURLToHistory(VRCUrl url)
|
|||
|
{
|
|||
|
if (currentURLField)
|
|||
|
{
|
|||
|
if (previousURLField)
|
|||
|
previousURLField.text = currentURLField.text;
|
|||
|
|
|||
|
currentURLField.text = url.Get();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void SetToVideoPlayerMode()
|
|||
|
{
|
|||
|
if (syncController)
|
|||
|
syncController.SetVideoVisual();
|
|||
|
}
|
|||
|
|
|||
|
public void SetToStreamPlayerMode()
|
|||
|
{
|
|||
|
if (syncController)
|
|||
|
syncController.SetStreamVisual();
|
|||
|
}
|
|||
|
|
|||
|
int _lastTime = int.MaxValue;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Updates UI elements such as the time readout, URL views, and seek bar
|
|||
|
/// </summary>
|
|||
|
void RunUIUpdate()
|
|||
|
{
|
|||
|
if (targetVideoPlayer.IsInVideoMode())
|
|||
|
{
|
|||
|
VideoPlayerManager manager = targetVideoPlayer.GetVideoManager();
|
|||
|
float duration = manager.GetDuration();
|
|||
|
|
|||
|
if (_draggingSlider)
|
|||
|
{
|
|||
|
float currentProgress = progressSlider.value;
|
|||
|
float currentTime = duration * currentProgress;
|
|||
|
|
|||
|
targetVideoPlayer.SeekTo(currentProgress);
|
|||
|
|
|||
|
string currentTimeStr = GetFormattedTime(System.TimeSpan.FromSeconds(currentTime));
|
|||
|
|
|||
|
if (statusTextField) statusTextField.text = currentTimeStr;
|
|||
|
if (statusTextDropShadow) statusTextDropShadow.text = currentTimeStr;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
float currentTime = manager.GetTime();
|
|||
|
|
|||
|
if (progressSlider)
|
|||
|
{
|
|||
|
if (duration > 0f)
|
|||
|
{
|
|||
|
progressSlider.gameObject.SetActive(true);
|
|||
|
progressSlider.value = Mathf.Clamp01(currentTime / duration);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
progressSlider.gameObject.SetActive(false);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
int currentTimeInt = Mathf.RoundToInt(currentTime);
|
|||
|
if (currentTimeInt != _lastTime)
|
|||
|
{
|
|||
|
_lastTime = currentTimeInt;
|
|||
|
|
|||
|
if (!float.IsInfinity(duration) & duration != float.MaxValue)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(_currentStatusText))
|
|||
|
{
|
|||
|
System.TimeSpan durationTimespan = System.TimeSpan.FromSeconds(duration);
|
|||
|
System.TimeSpan currentTimeTimespan = System.TimeSpan.FromSeconds(currentTime);
|
|||
|
|
|||
|
string totalTimeStr = GetFormattedTime(durationTimespan);
|
|||
|
string currentTimeStr = GetFormattedTime(currentTimeTimespan);
|
|||
|
|
|||
|
string statusStr = currentTimeStr + "/" + totalTimeStr;
|
|||
|
|
|||
|
if (statusTextField) statusTextField.text = statusStr;
|
|||
|
if (statusTextDropShadow) statusTextDropShadow.text = statusStr;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (progressSlider) progressSlider.gameObject.SetActive(false);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets a time string in the format hh:mm:ss, handles hours properly for long-running videos that wrap the hh section.
|
|||
|
/// </summary>
|
|||
|
/// <param name="time"></param>
|
|||
|
/// <returns></returns>
|
|||
|
string GetFormattedTime(System.TimeSpan time)
|
|||
|
{
|
|||
|
return ((int)time.TotalHours).ToString("D2") + time.ToString(@"\:mm\:ss");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Called when the user enters a URL in the url input field, forwards the input to the video player
|
|||
|
/// </summary>
|
|||
|
public void OnURLInput()
|
|||
|
{
|
|||
|
targetVideoPlayer.PlayVideo(urlField.GetUrl());
|
|||
|
urlField.SetUrl(VRCUrl.Empty);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Fired when the play button, pause button, or stop button are pressed.
|
|||
|
/// </summary>
|
|||
|
public void OnPlayButtonPress()
|
|||
|
{
|
|||
|
targetVideoPlayer.SetPaused(!targetVideoPlayer.IsPaused());
|
|||
|
}
|
|||
|
|
|||
|
public void OnLockButtonPress()
|
|||
|
{
|
|||
|
if (targetVideoPlayer.IsPrivilegedUser(Networking.LocalPlayer))
|
|||
|
{
|
|||
|
targetVideoPlayer.TakeOwnership();
|
|||
|
targetVideoPlayer.SetLocked(!targetVideoPlayer.IsLocked());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void OnReloadButtonPressed()
|
|||
|
{
|
|||
|
targetVideoPlayer.Reload();
|
|||
|
}
|
|||
|
|
|||
|
public void OnLoopButtonPressed()
|
|||
|
{
|
|||
|
targetVideoPlayer.TakeOwnership();
|
|||
|
targetVideoPlayer.SetLooping(!targetVideoPlayer.IsLooping());
|
|||
|
}
|
|||
|
|
|||
|
public void OnVideoPlayerModeButtonPressed()
|
|||
|
{
|
|||
|
targetVideoPlayer.SetToUnityPlayer();
|
|||
|
}
|
|||
|
|
|||
|
public void OnStreamPlayerModeButtonPressed()
|
|||
|
{
|
|||
|
targetVideoPlayer.SetToAVProPlayer();
|
|||
|
}
|
|||
|
|
|||
|
public void OnSeekSliderChanged()
|
|||
|
{
|
|||
|
//if (!_draggingSlider)
|
|||
|
// return;
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
bool _draggingSlider = false;
|
|||
|
|
|||
|
public void OnSeekSliderBeginDrag()
|
|||
|
{
|
|||
|
if (Networking.IsOwner(targetVideoPlayer.gameObject))
|
|||
|
_draggingSlider = true;
|
|||
|
}
|
|||
|
|
|||
|
public void OnSeekSliderEndDrag()
|
|||
|
{
|
|||
|
_draggingSlider = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|