XRoom_Unity/Assets/CanvasVideoPlayer/Scripts/VideoController.cs
2025-05-31 10:20:20 +03:30

305 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.Video;
namespace CanvasVideoPlayer.DamacServices {
[RequireComponent(typeof(VideoPlayer))]
public class VideoController : MonoBehaviour {
public Button PlayButton;
public Button PauseButton;
public Button ExitButton;
public RectTransform Knob;
public RectTransform ProgressBar;
public RectTransform ProgressBarBackground;
public TMP_Text MessagePlaceHoholder;
public GameObject LoadingObject;
public float MaxProgress =>
Vector2.Scale(ProgressBarBackground.rect.size, ProgressBarBackground.localScale).x;
public float MinProgress =>
-Knob.sizeDelta.x * .5f;
public float NormalizedKnobValue =>
Knob.anchoredPosition.x / MaxProgress;
public float Progress {
get => MaxProgress * NormalizedProgress;
set => Frame = (long)(m_videoPlayer.frameCount * value / MaxProgress);
}
public float NormalizedProgress {
get => Frame > 0 ? Frame / (float)m_videoPlayer.frameCount : float.Epsilon;
set => Frame = (long)(m_videoPlayer.frameCount * value);
}
long mlastPlayedFrame;
long m_frame;
public long Frame {
get => m_frame;
private set {
m_frame = Mathf.Clamp((int)value, 0, (int)m_videoPlayer.frameCount);
m_videoPlayer.frame = m_frame;
UpdateProgressBar();
}
}
public string Url {
get => m_videoPlayer.url;
set {
m_videoPlayer.url = value;
Frame = 0;
if (AutoPlay)
m_videoStates.SetState(PLAY_STATE);
}
}
public bool AutoPlay {
get => m_videoPlayer.playOnAwake;
set => m_videoPlayer.playOnAwake = value;
}
public event Action OnPlay;
public event Action OnPause;
public event Action OnExit;
public event Action OnUpdate;
const string LOADING_STATE = nameof(LOADING_STATE);
const string PREPARED_STATE = nameof(PREPARED_STATE);
const string PLAY_STATE = nameof(PLAY_STATE);
const string PAUSE_STATE = nameof(PAUSE_STATE);
const string SKIP_STATE = nameof(SKIP_STATE);
const string ERROR_STATE = nameof(ERROR_STATE);
const string EXIT_STATE = nameof(EXIT_STATE);
private VideoPlayer m_videoPlayer;
private SimpleStateMachine m_videoStates = new SimpleStateMachine();
private void Awake() {
m_videoPlayer = GetComponent<VideoPlayer>();
PlayButton.onClick.AddListener(Play);
PauseButton.onClick.AddListener(Pause);
ExitButton.onClick.AddListener(Exit);
var knobInput = Knob.gameObject
.AddComponent<ControlInput>();
knobInput.OnDown = KnobOnDown;
knobInput.OnUp = KnobOnUp;
knobInput.OnDrag = KnobOnDrag;
var progressInput = ProgressBarBackground.gameObject
.AddComponent<ControlInput>();
progressInput.OnDown = KnobOnDown;
progressInput.OnUp = KnobOnUp;
progressInput.OnDrag = KnobOnDrag;
m_videoPlayer.prepareCompleted += OnVideoPrepared;
m_videoPlayer.errorReceived += OnVideoError;
m_videoPlayer.loopPointReached += OnLoopPointReached;
m_videoStates.CreateState(
LOADING_STATE,
() => {
m_videoPlayer.targetTexture?.Release();
m_videoPlayer.Prepare();
UpdateUI();
});
m_videoStates.CreateState(
PREPARED_STATE,
() => {
if (AutoPlay)
Play();
UpdateUI();
});
m_videoStates.CreateState(
PLAY_STATE,
() => {
if (!m_videoPlayer.isPrepared)
Prepare();
else {
OnPlay?.Invoke();
m_videoPlayer.Play();
if (!m_videoPlayer.isPlaying)
Pause();
}
UpdateUI();
});
m_videoStates.CreateState(
PAUSE_STATE,
() => {
if (!m_videoPlayer.isPrepared)
Prepare();
else {
OnPause?.Invoke();
m_videoPlayer.Pause();
UpdateProgressBar();
UpdateUI();
}
});
m_videoStates.CreateState(
SKIP_STATE,
() => {
m_videoPlayer.Pause();
});
m_videoStates.CreateState(
ERROR_STATE,
() => UpdateUI());
m_videoStates.CreateState(
EXIT_STATE,
() => {
OnExit?.Invoke();
Destroy(gameObject);
});
m_videoStates.SetState(LOADING_STATE);
}
private void OnLoopPointReached(VideoPlayer source) {
m_videoStates.SetState(PAUSE_STATE);
}
private void OnVideoPrepared(VideoPlayer source) {
m_videoStates.SetState(PREPARED_STATE);
}
private void OnVideoError(VideoPlayer source, string message) {
m_videoStates.SetState(ERROR_STATE);
MessagePlaceHoholder?.SetText(message);
Debug.LogError(message);
}
private void OnDestroy() {
PlayButton.onClick.RemoveListener(Play);
PauseButton.onClick.RemoveListener(Pause);
ExitButton.onClick.RemoveListener(Exit);
}
private void Update() =>
OnUpdate?.Invoke();
void LateUpdate() {
if (m_videoStates.CurrentState == PLAY_STATE && m_videoPlayer.frame != mlastPlayedFrame) {
UpdateProgressBar();
m_frame = m_videoPlayer.frame;
mlastPlayedFrame = m_videoPlayer.frame;
}
}
void UpdateProgressBar() {
ProgressBar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Progress);
Knob.anchoredPosition = new Vector2(Progress + MinProgress, Knob.anchoredPosition.y);
}
void UpdateUI() {
PlayButton.gameObject.SetActive(m_videoStates.CurrentState != PLAY_STATE);
PauseButton.gameObject.SetActive(m_videoStates.CurrentState == PLAY_STATE);
LoadingObject.SetActive(m_videoStates.CurrentState == LOADING_STATE);
MessagePlaceHoholder?.gameObject.SetActive(m_videoStates.CurrentState == ERROR_STATE);
UpdateProgressBar();
}
void KnobOnDown(Component sender, Vector2 position) {
if ((sender.transform as RectTransform) == Knob)
m_videoStates.SetState(SKIP_STATE);
}
void KnobOnUp(Component sender, Vector2 position) {
if ((sender.transform as RectTransform) == Knob)
m_videoStates.GoToPreviousState();
}
void KnobOnDrag(Component sender, Vector2 position) {
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(ProgressBarBackground, position, null, out var newKnobPos))
Progress = newKnobPos.x + ProgressBarBackground.rect.width * .5f;
}
public void Prepare() =>
m_videoStates.SetState(LOADING_STATE);
public void Pause() =>
m_videoStates.SetState(PAUSE_STATE);
public void Play() =>
m_videoStates.SetState(PLAY_STATE);
public void Exit() =>
m_videoStates.SetState(EXIT_STATE);
public void SetUrl(TMP_InputField input) =>
Url = input.text;
}
#region Input
internal class ControlInput : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler {
public Action<Component, Vector2> OnDown;
public Action<Component, Vector2> OnUp;
public Action<Component, Vector2> OnDrag;
bool m_dragging;
void IPointerDownHandler.OnPointerDown(PointerEventData eventData) {
OnDown?.Invoke(this, eventData.position);
m_dragging = true;
}
void IPointerUpHandler.OnPointerUp(PointerEventData eventData) {
OnUp?.Invoke(this, eventData.position);
m_dragging = false;
}
private void Update() {
if (m_dragging)
OnDrag?.Invoke(this, Input.mousePosition);
}
void IDragHandler.OnDrag(PointerEventData eventData) { }
}
#endregion Input
#region StateMachine
internal class SimpleStateMachine {
Dictionary<string, Action> m_states = new Dictionary<string, Action>();
public string CurrentState {
get;
private set;
}
public string PreviousState {
get;
private set;
}
public void CreateState(string state, Action action, bool set = false) {
m_states.Add(state, action);
if (set)
SetState(state);
}
public void Dispose() {
m_states.Clear();
}
public void SetState(string state) {
if (state == CurrentState)
return;
Debug.Log($"Changed from state {CurrentState} to {state}");
PreviousState = CurrentState;
CurrentState = state;
m_states[CurrentState].Invoke();
}
public void GoToPreviousState() {
SetState(PreviousState);
}
}
#endregion StateMachine
}