Skip to content

Commit

Permalink
Merge branch 'master' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
Rene-Sackers committed Dec 14, 2023
2 parents 6b9e103 + 6f216c2 commit e1c4a26
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 87 deletions.
38 changes: 23 additions & 15 deletions src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,38 @@

<div class="viewer">
<div class="top-row">
<VideoPlayer @ref="_videoPlayerFront" Class="video video--front" Src="@_frontVideoSrc" VideoEnded="FrontVideoEnded" TimeUpdate="FrontVideoTimeUpdate" />
<VideoPlayer @key="@("128D7AB3")" @ref="_videoPlayerFront" Class="video video--front" VideoEnded="VideoEnded" TimeUpdate="FrontVideoTimeUpdate" />
</div>
<div class="bottom-row">
<VideoPlayer @ref="_videoPlayerLeftRepeater" Class="video video--left-repeater" Src="@_leftRepeaterVideoSrc" />
<VideoPlayer @ref="_videoPlayerBack" Class="video video--back" Src="@_backVideoSrc" />
<VideoPlayer @ref="_videoPlayerRightRepeater" Class="video video--right-repeater" Src="@_rightRepeaterVideoSrc" />
<VideoPlayer @key="@("D1916B24")" @ref="_videoPlayerLeftRepeater" Class="video video--left-repeater" VideoEnded="VideoEnded" />
<VideoPlayer @key="@("66EC38D4")" @ref="_videoPlayerBack" Class="video video--back" VideoEnded="VideoEnded" />
<VideoPlayer @key="@("87B15DCA")" @ref="_videoPlayerRightRepeater" Class="video video--right-repeater" VideoEnded="VideoEnded" />
</div>
<div class="controls">
<div class="play-pause">
<MudIconButton Icon="@Icons.Material.Filled.SkipPrevious" Size="Size.Medium" OnClick="@PreviousButtonClicked" />
<MudIconButton Icon="@(_isPlaying ? Icons.Material.Filled.PauseCircleOutline : Icons.Material.Filled.PlayCircleOutline)" Size="Size.Large" OnClick="PlayPauseClicked" />
<MudIconButton Icon="@(_isPlaying ? Icons.Material.Filled.PauseCircleOutline : Icons.Material.Filled.PlayCircleOutline)" Size="Size.Large" OnClick="@PlayPauseClicked" />
<MudIconButton Icon="@Icons.Material.Filled.SkipNext" Size="Size.Medium" OnClick="@NextButtonClicked" />
</div>
<div class="seeker-slider-container">
<MudSlider
T="double"
@ref="_timelineSlider"
Max="@_timelineMaxSeconds"
Step="0.01"
@bind-Value="@TimelineValue"
Variant="Variant.Filled"
@onpointerdown="@TimelineSliderMouseDown"
@onpointerup="@TimelineSliderMouseUp"/>
<div class="event-marker" style="@EventMarkerStyle()"></div>
<div class="time-container">@_clip?.StartDate.ToString("hh:mm:ss tt")</div>
<div class="slider-container">
<MudSlider
T="double"
@ref="_timelineSlider"
Max="@_timelineMaxSeconds"
Step="0.01"
@bind-Value="@TimelineValue"
Variant="Variant.Filled"
@onpointerdown="@TimelineSliderPointerDown"
@onpointerup="@TimelineSliderPointerUp"/>
<div class="event-marker" style="@EventMarkerStyle()"></div>
@foreach (var segment in _clip?.Segments ?? Array.Empty<ClipVideoSegment>())
{
<div class="segment-marker" style="@SegmentStartMargerStyle(segment)"></div>
}
</div>
<div class="time-container">@_clip?.EndDate.ToString("hh:mm:ss tt")</div>
</div>
</div>
</div>
178 changes: 129 additions & 49 deletions src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System.Timers;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
Expand Down Expand Up @@ -31,14 +35,11 @@ private double TimelineValue
}

private Clip _clip;
private string _frontVideoSrc;
private string _leftRepeaterVideoSrc;
private string _rightRepeaterVideoSrc;
private string _backVideoSrc;
private VideoPlayer _videoPlayerFront;
private VideoPlayer _videoPlayerLeftRepeater;
private VideoPlayer _videoPlayerRightRepeater;
private VideoPlayer _videoPlayerBack;
private int _videoLoadedEventCount = 0;
private bool _isPlaying;
private ClipVideoSegment _currentSegment;
private MudSlider<double> _timelineSlider;
Expand All @@ -48,6 +49,7 @@ private double TimelineValue
private bool _isScrubbing;
private double _timelineValue;
private System.Timers.Timer _setVideoTimeDebounceTimer;
private CancellationTokenSource _loadSegmentCts = new();

protected override void OnInitialized()
{
Expand All @@ -59,78 +61,137 @@ protected override void OnAfterRender(bool firstRender)
{
if (!firstRender)
return;

_videoPlayerFront.Loaded += () =>
{
Console.WriteLine("Loaded: Front");
_videoLoadedEventCount++;
};
_videoPlayerLeftRepeater.Loaded += () =>
{
Console.WriteLine("Loaded: Left");
_videoLoadedEventCount++;
};
_videoPlayerRightRepeater.Loaded += () =>
{
Console.WriteLine("Loaded: Right");
_videoLoadedEventCount++;
};
_videoPlayerBack.Loaded += () =>
{
Console.WriteLine("Loaded: Back");
_videoLoadedEventCount++;
};
}

private static Task AwaitUiUpdate()
=> Task.Delay(100);

public async Task SetClipAsync(Clip clip)
{
_clip = clip;
TimelineValue = 0;
_timelineMaxSeconds = (clip.EndDate - clip.StartDate).TotalSeconds;

_currentSegment = _clip.Segments.First();
SetCurrentSegmentVideos();

if (_isPlaying)
{
// Let elements update
await Task.Delay(100);
await ToggleSetPlayingAsync(true);
}
await SetCurrentSegmentVideosAsync();
}

private void SetCurrentSegmentVideos()
private async Task<bool> SetCurrentSegmentVideosAsync()
{
if (_currentSegment == null)
return;
return false;

await _loadSegmentCts.CancelAsync();
_loadSegmentCts = new();

_frontVideoSrc = _currentSegment.CameraFront?.Url;
_leftRepeaterVideoSrc = _currentSegment.CameraLeftRepeater?.Url;
_rightRepeaterVideoSrc = _currentSegment.CameraRightRepeater?.Url;
_backVideoSrc = _currentSegment.CameraBack?.Url;
_videoLoadedEventCount = 0;
var cameraCount = _currentSegment.CameraAnglesCount();

var wasPlaying = _isPlaying;
if (wasPlaying)
await TogglePlayingAsync(false);

StateHasChanged();
_videoPlayerFront.Src = _currentSegment.CameraFront?.Url;

Check warning on line 115 in src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor.cs

View workflow job for this annotation

GitHub Actions / windows-selfcontained

Component parameter 'Src' should not be set outside of its component.
_videoPlayerLeftRepeater.Src = _currentSegment.CameraLeftRepeater?.Url;

Check warning on line 116 in src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor.cs

View workflow job for this annotation

GitHub Actions / windows-selfcontained

Component parameter 'Src' should not be set outside of its component.
_videoPlayerRightRepeater.Src = _currentSegment.CameraRightRepeater?.Url;

Check warning on line 117 in src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor.cs

View workflow job for this annotation

GitHub Actions / windows-selfcontained

Component parameter 'Src' should not be set outside of its component.
_videoPlayerBack.Src = _currentSegment.CameraBack?.Url;

Check warning on line 118 in src/TeslaCamPlayer.BlazorHosted/Client/Components/ClipViewer.razor.cs

View workflow job for this annotation

GitHub Actions / windows-selfcontained

Component parameter 'Src' should not be set outside of its component.

if (_loadSegmentCts.IsCancellationRequested)
return false;

await InvokeAsync(StateHasChanged);

var timeout = Task.Delay(10000);
var completedTask = await Task.WhenAny(Task.Run(async () =>
{
while (_videoLoadedEventCount < cameraCount && !_loadSegmentCts.IsCancellationRequested)
await Task.Delay(10, _loadSegmentCts.Token);

Console.WriteLine("Loading done");
}, _loadSegmentCts.Token), timeout);

if (completedTask == timeout)
{
Console.WriteLine("Loading timed out");
return false;
}

if (wasPlaying)
await TogglePlayingAsync(true);

return !_loadSegmentCts.IsCancellationRequested;
}

private async Task ExecuteOnPlayers(Func<VideoPlayer, Task> player)
private async Task ExecuteOnPlayers(Func<VideoPlayer, Task> action)
{
try
{
await player(_videoPlayerFront);
await player(_videoPlayerLeftRepeater);
await player(_videoPlayerRightRepeater);
await player(_videoPlayerBack);
await action(_videoPlayerFront);
await action(_videoPlayerLeftRepeater);
await action(_videoPlayerRightRepeater);
await action(_videoPlayerBack);
}
catch
{
// ignore
}
}

private async Task ToggleSetPlayingAsync(bool? play = null)
private Task TogglePlayingAsync(bool? play = null)
{
play ??= !_isPlaying;
_isPlaying = play.Value;
await ExecuteOnPlayers(async p => await (play.Value ? p.PlayAsync() : p.PauseAsync()));
return ExecuteOnPlayers(async p => await (play.Value ? p.PlayAsync() : p.PauseAsync()));
}

private Task PlayPauseClicked()
=> ToggleSetPlayingAsync();
=> TogglePlayingAsync();

private async Task FrontVideoEnded()
private async Task VideoEnded()
{
if (_currentSegment == _clip.Segments.Last())
{
_isPlaying = false;
return;
}

_currentSegment = _clip.Segments
await TogglePlayingAsync(false);

var nextSegment = _clip.Segments
.OrderBy(s => s.StartDate)
.SkipWhile(s => s != _currentSegment)
.Skip(1)
.First();
SetCurrentSegmentVideos();
await Task.Delay(10);
await ToggleSetPlayingAsync(true);
.FirstOrDefault()
?? _clip.Segments.FirstOrDefault();

if (nextSegment == null)
{
await TogglePlayingAsync(false);
return;
}

_currentSegment = nextSegment;
await SetCurrentSegmentVideosAsync();
await AwaitUiUpdate();
await TogglePlayingAsync(true);
}

private async Task FrontVideoTimeUpdate()
Expand All @@ -149,22 +210,25 @@ private async Task FrontVideoTimeUpdate()
TimelineValue = secondsSinceClipStart;
}

private async Task TimelineSliderMouseDown()
private async Task TimelineSliderPointerDown()
{
_isScrubbing = true;
_wasPlayingBeforeScrub = _isPlaying;
await ToggleSetPlayingAsync(false);
await TogglePlayingAsync(false);

// Allow value change event to trigger, then scrub before user releases mouse click
await Task.Delay(10);
await AwaitUiUpdate();
await ScrubToSliderTime();
}

private async Task TimelineSliderMouseUp()
private async Task TimelineSliderPointerUp()
{
Console.WriteLine("Pointer up");
await ScrubToSliderTime();
_isScrubbing = false;
if (_wasPlayingBeforeScrub)
await ToggleSetPlayingAsync(true);

if (!_isPlaying && _wasPlayingBeforeScrub)
await TogglePlayingAsync(true);
}

private async void ScrubVideoDebounceTick(object _, ElapsedEventArgs __)
Expand All @@ -180,11 +244,17 @@ private async Task ScrubToSliderTime()
try
{
var scrubToDate = _clip.StartDate.AddSeconds(TimelineValue);
var segment = _clip.SegmentAtDate(scrubToDate);
var segment = _clip.SegmentAtDate(scrubToDate)
?? _clip.Segments.Where(s => s.StartDate > scrubToDate).MinBy(s => s.StartDate);

if (segment == null)
return;

if (segment != _currentSegment)
{
_currentSegment = segment;
await InvokeAsync(SetCurrentSegmentVideos);
if (!await SetCurrentSegmentVideosAsync())
return;
}

var secondsIntoSegment = (scrubToDate - segment.StartDate).TotalSeconds;
Expand All @@ -196,14 +266,24 @@ private async Task ScrubToSliderTime()
}
}

private double DateTimeToTimelinePercentage(DateTime dateTime)
{
var percentage = Math.Round(dateTime.Subtract(_clip.StartDate).TotalSeconds / _clip.TotalSeconds * 100, 2);
return Math.Clamp(percentage, 0, 100);
}

private string SegmentStartMargerStyle(ClipVideoSegment segment)
{
var percentage = DateTimeToTimelinePercentage(segment.StartDate);
return $"left: {percentage}%";
}

private string EventMarkerStyle()
{
if (_clip?.Event?.Timestamp == null)
return "display: none";

var percentageOfClipAtTimestamp = Math.Round(_clip.Event.Timestamp.Subtract(_clip.StartDate).TotalSeconds / _clip.TotalSeconds * 100, 2);
percentageOfClipAtTimestamp = Math.Clamp(percentageOfClipAtTimestamp, 0, 100);

return $"left: {percentageOfClipAtTimestamp}%";
var percentage = DateTimeToTimelinePercentage(_clip.Event.Timestamp);
return $"left: {percentage}%";
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<video
@key="@Src"
@ref="_player"
src="@Src"
class="@Class
@(string.IsNullOrWhiteSpace(Src) ? "d-none" : null)"
@onended="@VideoEndedHandler"
@ontimeupdate="@TimeUpdateHandler"
@oncanplaythrough="@OnLoadedData"
disableremoteplayback></video>

@code {
Expand All @@ -23,6 +25,10 @@
[Parameter]
public EventCallback TimeUpdate { get; set; }

public delegate void LoadedHandler();

public event LoadedHandler Loaded;

private ElementReference _player;

public ValueTask PlayAsync()
Expand All @@ -42,4 +48,8 @@

private Task TimeUpdateHandler()
=> TimeUpdate.InvokeAsync();

private void OnLoadedData()
=> Loaded?.Invoke();

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>warnings</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.11.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

Expand Down
Loading

0 comments on commit e1c4a26

Please sign in to comment.