Created
June 5, 2018 08:54
-
-
Save Frooxius/a8b62f9cdc9539943922f55ef215ef4c to your computer and use it in GitHub Desktop.
Video Player UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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