Skip to content

Instantly share code, notes, and snippets.

@Frooxius
Created June 5, 2018 08:54
Show Gist options
  • Save Frooxius/a8b62f9cdc9539943922f55ef215ef4c to your computer and use it in GitHub Desktop.
Save Frooxius/a8b62f9cdc9539943922f55ef215ef4c to your computer and use it in GitHub Desktop.
Video Player UI
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BaseX;
namespace FrooxEngine
{
[Category("Media/Players")]
public class VideoPlayer : Component, IMaterialSource, IMaterialApplyPolicy, IPlayable
{
public override int Version => 2;
const float SPACING = 0.025f;
const float SLIDER_HEIGHT = 0.075f;
const float THICKNESS = 0.01f;
const float CURSOR_WIDTH = 0.075f;
const float TRACK_RATIO = 0.75f;
const float TRACK_SPACING = 0.01f;
const float INDICATOR_SIZE = 0.2f;
bool IMaterialApplyPolicy.CanApplyMaterial => false;
public float NormalizedVideoPosition
{
get => videoProvider.Target?.NormalizedPosition ?? -1;
set
{
if(videoProvider.Target?.IsAssetAvailable ?? false)
videoProvider.Target.NormalizedPosition = value;
}
}
public VideoTextureProvider Video
{
get => videoProvider.Target;
set => videoProvider.Target = value;
}
public Uri VideoURL
{
get => videoProvider.Target?.URL.Value;
set
{
bool stream = videoProvider.Target?.Stream.Value ?? false;
videoProvider.Target?.Destroy();
videoProvider.Target = Slot.AttachComponent<VideoTextureProvider>();
videoProvider.Target.Stream.Value = stream;
videoProvider.Target.URL.Value = value;
}
}
public readonly Sync<StereoLayout> StereoLayout;
readonly SyncRef<VideoTextureProvider> videoProvider;
readonly FieldDrive<color> _lightColor;
readonly SyncRef<NeosUIStyle> _style;
readonly FieldDrive<Uri> _indicatorTextureUrl;
readonly FieldDrive<color> _indicatorTint;
readonly FieldDrive<float3> _colliderSize;
readonly FieldDrive<float> _frameWidth;
readonly FieldDrive<float> _frameHeight;
readonly DriveRef<PBS_RimMetallic> _frameMaterial;
readonly SyncRef<UnlitMaterial> _displayMaterial;
readonly FieldDrive<float2> _displaySize;
readonly SyncRef<NeosSlider> _timelineSlider;
readonly FieldDrive<float3> _timelinePosition;
readonly FieldDrive<float> _timelineWidth;
readonly FieldDrive<float> _positionDrive;
readonly SyncRef<NeosSlider> _volumeSlider;
readonly FieldDrive<float3> _volumePosition;
readonly FieldDrive<float> _volumeWidth;
readonly FieldDrive<float> _volumeDrive;
readonly FieldDrive<float> _buttonsWidth;
readonly FieldDrive<float> _buttonsHeight;
readonly FieldDrive<float3> _buttonsPosition;
readonly FieldDrive<color> _playButtonColor;
readonly FieldDrive<color> _pauseButtonColor;
readonly FieldDrive<color> _stopButtonColor;
readonly FieldDrive<color> _loopButtonColor;
bool _indicatorIsPlaying;
public float2 Size
{
get
{
var baseSize = new float2(16, 9);
if (IsReady)
baseSize = Video.Asset.Size;
return baseSize / baseSize.x;
}
}
public bool IsReady => Video?.IsAssetAvailable ?? false;
public IAssetProvider<Material> Material => _displayMaterial.Target;
public float Position { get => ((IPlayable)Video).Position; set => ((IPlayable)Video).Position = value; }
public float Speed { get => ((IPlayable)Video).Speed; set => ((IPlayable)Video).Speed = value; }
public float ClipLength => Video.ClipLength;
public bool IsStreaming => Video.IsStreaming;
public bool IsPlaying => ((IPlayable)Video).IsPlaying;
public bool IsFinished => ((IPlayable)Video).IsFinished;
public float NormalizedPosition { get => ((IPlayable)Video).NormalizedPosition; set => ((IPlayable)Video).NormalizedPosition = value; }
public bool Loop { get => ((IPlayable)Video).Loop; set => ((IPlayable)Video).Loop = value; }
public void PlayWhenReady()
{
StartCoroutine(PlayWhenReadyCo());
}
IEnumerator<Context> PlayWhenReadyCo()
{
while (!IsReady)
yield return Context.WaitForNextUpdate();
Video?.Play();
}
protected override void OnAwake()
{
_positionDrive.SetupValueSetHook((field, value) => NormalizedVideoPosition = value);
_volumeDrive.SetupValueSetHook((field, value) =>
{
if (Video != null)
Video.Volume.Value = value;
});
}
protected override void OnAttach()
{
Slot.AttachComponent<ObjectRoot>();
Slot.AttachComponent<Grabbable>().Scalable = true;
Slot.LocalScale = float3.One * 0.5f;
_style.Target = Slot.AttachComponent<NeosUIStyle>();
var frame = Slot.AddSlot("Frame");
var display = Slot.AddSlot("Display");
var timeline = Slot.AddSlot("Timeline");
var volume = Slot.AddSlot("Volume");
var indicator = Slot.AddSlot("Indicator");
var buttons = Slot.AddSlot("Buttons");
// Display
display.LocalPosition += float3.Backward * 0.001f;
var displayModel = display.AttachMesh<QuadMesh, UnlitMaterial>();
_displayMaterial.Target = displayModel.material;
_displaySize.Target = displayModel.mesh.Size;
// Frame
var collider = frame.AttachComponent<BoxCollider>();
_colliderSize.Target = collider.Size;
var frameModel = frame.AttachMesh<BevelSoliStripeMesh, PBS_RimMetallic>();
_frameMaterial.Target = frameModel.material;
frameModel.mesh.Thickness = THICKNESS;
frameModel.mesh.Relief = true;
frameModel.mesh.Slant = 0f;
_frameWidth.Target = frameModel.mesh.Width_Field;
_frameHeight.Target = frameModel.mesh.Height_Field;
// Indicator
indicator.LocalPosition += float3.Backward * 0.005f;
var indicatorModel = indicator.AttachMesh<QuadMesh, UnlitMaterial>();
var indicatorTexture = indicator.AttachComponent<StaticTexture2D>();
indicatorTexture.URL.Value = NeosAssets.Common.Indicators.Pause;
indicatorModel.material.BlendMode.Value = BlendMode.Alpha;
indicatorModel.material.Texture.Target = indicatorTexture;
indicatorModel.mesh.Size.Value = float2.One * INDICATOR_SIZE;
_indicatorTextureUrl.Target = indicatorTexture.URL;
_indicatorTint.Target = indicatorModel.material.TintColor;
var indicatorCollider = indicator.AttachComponent<BoxCollider>();
indicatorCollider.Size.Value = new float3(INDICATOR_SIZE, INDICATOR_SIZE, 0f);
var indicatorTouchRelay = indicator.AttachComponent<TouchEventRelay>();
indicatorTouchRelay.Touched.Target = OnIndicatorTouched;
// Sliders
volume.LocalRotation = floatQ.AxisAngle(float3.Forward, 90f);
_timelineSlider.Target = timeline.AttachComponent<NeosSlider>();
_volumeSlider.Target = volume.AttachComponent<NeosSlider>();
_timelineSlider.Target.Style = _style.Target;
_volumeSlider.Target.Style = _style.Target;
_timelineSlider.Target.Symmetrical = true;
_volumeSlider.Target.Symmetrical = true;
_timelineSlider.Target.Min = 0f;
_timelineSlider.Target.Max = 1f;
_volumeSlider.Target.Min = 0f;
_volumeSlider.Target.Max = 1f;
_timelinePosition.Target = timeline.Position_Field;
_timelineWidth.Target = _timelineSlider.Target.WidthField;
_positionDrive.Target = _timelineSlider.Target.Value_Field;
_volumePosition.Target = volume.Position_Field;
_volumeWidth.Target = _volumeSlider.Target.WidthField;
_volumeDrive.Target = _volumeSlider.Target.Value_Field;
_volumeSlider.Target.Height = SLIDER_HEIGHT;
_volumeSlider.Target.Thickness = THICKNESS;
_volumeSlider.Target.CursorWidth = CURSOR_WIDTH;
_volumeSlider.Target.TrackRatio = TRACK_RATIO;
_volumeSlider.Target.Spacing = TRACK_SPACING;
_volumeSlider.Target.Slant *= -1;
_timelineSlider.Target.Height = SLIDER_HEIGHT;
_timelineSlider.Target.Thickness = THICKNESS;
_timelineSlider.Target.CursorWidth = CURSOR_WIDTH;
_timelineSlider.Target.TrackRatio = TRACK_RATIO;
_timelineSlider.Target.Spacing = TRACK_SPACING;
_timelineSlider.Target.Slant *= -1;
/*_displayMaterial.Target.EmissiveColor.Value = color.White;
_displayMaterial.Target.AlbedoColor.Value = color.Black;
_displayMaterial.Target.Metallic.Value = 0f;
_displayMaterial.Target.Smoothness.Value = 0.2f;*/
_timelineSlider.Target.IncrementPressed.Target = FastForward;
_timelineSlider.Target.DecrementPressed.Target = FastBackward;
_volumeSlider.Target.IncrementPressed.Target = VolumeUp;
_volumeSlider.Target.DecrementPressed.Target = VolumeDown;
// Buttons
buttons.LocalRotation = floatQ.AxisAngle(float3.Forward, -90f);
var choiceBar = buttons.AttachComponent<NeosHorizontalChoiceBar>();
choiceBar.Symmetrical.Value = true;
var play = choiceBar.AddItem();
var pause = choiceBar.AddItem();
var stop = choiceBar.AddItem();
var loop = choiceBar.AddItem();
play.Text.Text = "Play";
pause.Text.Text = "Pause";
stop.Text.Text = "Stop";
loop.Text.Text = "Loop";
_playButtonColor.Target = play.OverrideColor;
_pauseButtonColor.Target = pause.OverrideColor;
_stopButtonColor.Target = stop.OverrideColor;
_loopButtonColor.Target = loop.OverrideColor;
play.Touched.Target = OnPlayTouched;
pause.Touched.Target = OnPauseTouched;
stop.Touched.Target = OnStopTouched;
loop.Touched.Target = OnLoopTouched;
_buttonsHeight.Target = choiceBar.Height;
_buttonsWidth.Target = choiceBar.Width;
_buttonsPosition.Target = buttons.Position_Field;
}
void OnIndicatorTouched(ITouchable touchedTouchable, TouchEventInfo eventInfo)
{
if (eventInfo.touch == EventState.Begin)
{
if (Video?.IsAssetAvailable ?? false)
{
if (Video.IsPlaying)
Video.Pause();
else if (Video.IsFinished)
Video.Play();
else
Video.Resume();
}
}
}
void OnPlayTouched(ITouchable touchable, TouchEventInfo eventInfo)
{
if(eventInfo.touch == EventState.Begin)
{
if (Video?.IsAssetAvailable ?? false)
Video.Resume();
}
}
void OnPauseTouched(ITouchable touchable, TouchEventInfo eventInfo)
{
if (eventInfo.touch == EventState.Begin)
{
if (Video?.IsAssetAvailable ?? false)
Video.Pause();
}
}
void OnStopTouched(ITouchable touchable, TouchEventInfo eventInfo)
{
if (eventInfo.touch == EventState.Begin)
{
if (Video?.IsAssetAvailable ?? false)
Video.Stop();
}
}
void OnLoopTouched(ITouchable touchable, TouchEventInfo eventInfo)
{
if (eventInfo.touch == EventState.Begin)
{
if (Video?.IsAssetAvailable ?? false)
Video.Loop = !Video.Loop;
}
}
void VolumeUp(NeosSlider slider)
{
if (Video?.IsAssetAvailable ?? false)
Video.Volume.TweenTo(MathX.Clamp01(Video.Volume + 0.1f), 0.25f);
}
void VolumeDown(NeosSlider slider)
{
if (Video?.IsAssetAvailable ?? false)
Video.Volume.TweenTo(MathX.Clamp01(Video.Volume - 0.1f), 0.25f);
}
void FastForward(NeosSlider slider)
{
if(Video?.IsAssetAvailable ?? false)
Video.Position += 10f;
}
void FastBackward(NeosSlider slider)
{
if (Video?.IsAssetAvailable ?? false)
Video.Position -= 10f;
}
protected override void OnChanges()
{
//_displayMaterial.Target.EmissiveMap.Target = videoProvider.Target;
_displayMaterial.Target.Texture.Target = videoProvider.Target;
ImageImporter.SetupStereoLayout(_displayMaterial.Target, StereoLayout.Value);
}
protected override void OnCommonUpdate()
{
var size = Size;
_displaySize.Target.Value = size;
// Add the relief thickness to the size
size += THICKNESS;
_colliderSize.Target.Value = size;
_timelineSlider.Target.TotalWidth = size.x;
_volumeSlider.Target.TotalWidth = size.y;
_frameWidth.Target.Value = size.x;
_frameHeight.Target.Value = size.y;
_style.Target.UpdateMaterial(_frameMaterial.Target, color.Clear, true, true);
_volumePosition.Target.Value = float3.Right * (size.x * 0.5f + SPACING + SLIDER_HEIGHT * 0.5f);
_timelinePosition.Target.Value = float3.Down * (size.y * 0.5f + SPACING + SLIDER_HEIGHT * 0.5f);
_positionDrive.Target.Value = MathX.Clamp01(NormalizedVideoPosition);
_volumeDrive.Target.Value = MathX.Clamp01(Video?.Volume.Value ?? 0f);
// Update the buttons
_buttonsHeight.Target.Value = SLIDER_HEIGHT;
_buttonsWidth.Target.Value = size.y;
_buttonsPosition.Target.Value = float3.Left * (size.x * 0.5f + SPACING + SLIDER_HEIGHT * 0.5f);
// Update the indicator
var isPlaying = Video?.IsPlaying ?? false;
var isStopped = !isPlaying && NormalizedVideoPosition == 0f;
var isPaused = !isPlaying && NormalizedVideoPosition != 0f;
var isLooping = Video?.Loop ?? false;
if(isPlaying != _indicatorIsPlaying)
{
_indicatorTextureUrl.Target.Value = isPlaying ? NeosAssets.Common.Indicators.Play : NeosAssets.Common.Indicators.Pause;
_indicatorIsPlaying = isPlaying;
}
_indicatorTint.Target.Value = new color(1f, MathX.Progress01(_indicatorTint.Target.Value.a,
Time.Delta * 4f, !isPlaying));
_playButtonColor.Target.Value = isPlaying ? color.Green : color.Red;
_stopButtonColor.Target.Value = isStopped ? color.Green : color.Red;
_pauseButtonColor.Target.Value = isPaused ? color.Green : color.Red;
_loopButtonColor.Target.Value = isLooping ? color.Green : color.Red;
}
public static VideoPlayer CreateVideoPlayer(Slot slot, Uri videoUri, bool stream = false)
{
var videoPlayer = slot.AttachComponent<VideoPlayer>();
var videoProvider = slot.AttachComponent<VideoTextureProvider>();
videoProvider.URL.Value = videoUri;
videoProvider.Stream.Value = stream;
videoPlayer.Video = videoProvider;
return videoPlayer;
}
protected override void OnLoaded(DataTreeNode node, LoadControl control)
{
if(control.GetTypeVersion(typeof(VideoPlayer)) < Version)
{
RunSynchronously(() =>
{
var s = Slot.Parent.AddSlot(Slot.Name);
s.CopyTransform(Slot);
var _video = Video;
if(Video.Slot.IsChildOf(Slot, true))
{
var newVideo = s.AttachComponent<VideoTextureProvider>();
newVideo.CopyValues(Video);
_video = newVideo;
}
var newPlayer = s.AttachComponent<VideoPlayer>();
newPlayer.Video = _video;
newPlayer.StereoLayout.Value = StereoLayout;
Slot.Destroy();
});
}
}
public void Play() => Video?.Play();
public void Stop() => Video?.Stop();
public void Pause() => Video?.Pause();
public void Resume() => Video?.Resume();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment