Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/video queue #23

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The easiest html5 `video` implementation for [Blazor](https://blazor.net) applic
![Screenshot of the component in action](screenshot.png)

## Changelog

### 2022-24-12 Version 1.1
- Bump dotnet version to 6.0 as 3.x and 5.x are now out of support.
- Add standard Methods and Properties (big thanks to https://github.com/JPVenson) and Async versions (for Server/WASM). (Issues #17 #9)
Expand Down Expand Up @@ -243,6 +244,66 @@ _Note: Attempting to read/write Properties from Blazor Server will throw a runti
Example - Remote JS (Server) and WASM
`int duration = await videoRef.GetDurationAsync()`

## Video Queue
> Hint: When using the `VideoQueue` component, the `BlazoredVideo.EndedEvent` is unavailable as it is utilized by the `VideoQueue`. You must instead use the `VideoQueue.OnNextPlayed` or `OnNextPlayed.OnPlaylistEnded` events.

By using the `<VideoQueue>` component instead of setting your `<source />` directly you can create a queue of videos that will be played sequentially. The VideoQueue supports multiple versions of each source and can be set to different repeat behaviors:

Example Simple queue
```razor
<BlazoredVideo>
<VideoQueue Delay="0" Repeat="No">
<VideoItem Source="videos/elephants.mp4" type="video/mp4" />
<VideoItem Source="videos/chimpanese.mp4" type="video/mp4" />
<VideoItem Source="videos/turtles.mp4" type="video/mp4" />
</VideoQueue>
</BlazoredVideo>
```
The simple queue will play all 3 videos after each other and then stop. It is possible to control the repeat behavior by setting the Repeat property.

| Repeat | Description
| --- | --- |
| NoLoop | Plays all videos in order and stops after the last one
| LoopOne | Repeats the current video forever
| LoopAll | Starts at the beginning of the queue after the last video was played

It is also possible to control the VideoQueue directly by obtaining the VideoQueue reference and invoking ether `PlayNext` or `PlayPrevious` like this:
```razor
<BlazoredVideo>
<VideoQueue @ref="videoQueue">
<VideoItem Source="videos/elephants.mp4" type="video/mp4" />
<VideoItem Source="videos/chimpanese.mp4" type="video/mp4" />
<VideoItem Source="videos/turtles.mp4" type="video/mp4" />
</VideoQueue>
</BlazoredVideo>


<div class="d-flex flex-row">
<button @onclick="() => videoQueue.PlayNext()">Next</button>
<button @onclick="() => videoQueue.PlayPrevious()">Previous</button>
</div>

@code
{
VideoQueue videoQueue;
}

```

To provide multiple versions of your video you can create a `VideoSource` under each `VideoItem`.
```razor
<BlazoredVideo>
<VideoQueue @ref="videoQueue">
<VideoItem>
<VideoSource Source="videos/elephants.mp4" type="video/mp4" />
<VideoSource Source="videos/elephants.ogg" type="video/ogg" />
</VideoItem>
<VideoItem Source="videos/chimpanese.mp4" type="video/mp4" />
<VideoItem Source="videos/turtles.mp4" type="video/mp4" />
</VideoQueue>
</BlazoredVideo>
```

### Customising the html

The Video can be customised using standard CSS techniques.
Expand Down
82 changes: 82 additions & 0 deletions samples/SharedRCL/Pages/QueueItems.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@page "/queue"

<h1>Blazored Video Demo - Queue</h1>

<div class="d-flex flex-row">
<BlazoredVideo class="w-40"
autoplay="autoplay"
style="max-width: 300px;">
<VideoQueue @ref="videoQueue"
OnNextPlayedEvent="StateHasChanged"
OnPlaylistEndedEvent="StateHasChanged"
Delay="Delay"
Repeat="Repeat">
<VideoItem Source="video/elephants2.mp4#t=3" Type="video/mp4"/>
<VideoItem>
<VideoSource Source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" Type="video/mp4" />
</VideoItem>
<VideoItem Source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" Type="video/mp4" />
<VideoItem Source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4" Type="video/mp4" />
</VideoQueue>
</BlazoredVideo>


@if (videoQueue != null)
{
<div class="list-group">
@foreach (var videos in videoQueue.VideoItems)
{
<div class="list-group-item selectable @((videos == videoQueue.CurrentItem ? "list-group-item-info" : ""))"
@onclick="() => videoQueue.Play(videos)">
<span>@(videos.VideoSourceData.First().Source.Split('/').Last())</span>
</div>
}
</div>
}

</div>


<div class="d-flex flex-row">
<button @onclick="() => videoQueue.PlayNext()">Next</button>
<button @onclick="() => videoQueue.PlayPrevious()">Previous</button>
</div>

<br/>
<EditForm Model="this">
<div class="form-group">
<label for="repeat">Repeat: </label>

<InputSelect @bind-Value="Repeat" id="repeat">
@foreach (var repeatVal in Enum.GetValues(typeof(RepeatValues)))
{
<option value="@repeatVal">@repeatVal</option>
}
</InputSelect>
</div>
<div class="form-group">
<label for="repeat">Delay: </label>
<InputNumber @bind-Value="Delay" id="delay"/>
</div>
</EditForm>

@code
{
VideoQueue videoQueue;

public RepeatValues Repeat { get; set; }
public int Delay { get; set; }

protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
SetupDisplay();
}
}

private void SetupDisplay()
{
StateHasChanged();
}
}
5 changes: 5 additions & 0 deletions samples/SharedRCL/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
<span class="oi oi-plus" aria-hidden="true"></span> Multiple Videos
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="queue">
<span class="oi oi-plus" aria-hidden="true"></span> Queueing
</NavLink>
</li>
</ul>
</div>

Expand Down
4 changes: 3 additions & 1 deletion src/Blazored.Video/BlazoredVideo.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@
}
}
*@
<video id="@UniqueKey" @key="UniqueKey" @attributes=@Attributes @onchange=@OnChange @ref="videoRef">@ChildContent</video>
<CascadingValue Value="this">
<video id="@UniqueKey" @key="UniqueKey" @attributes=@Attributes @onchange=@OnChange @ref="videoRef">@ChildContent</video>
</CascadingValue>
8 changes: 5 additions & 3 deletions src/Blazored.Video/BlazoredVideo.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ async Task Implement(VideoEvents eventName)
{
await jsModule.InvokeVoidAsync("registerCustomEventHandler", videoRef, eventName.ToString().ToLower(), options.GetPayload());
}
catch (Exception ex)
{
catch (Exception ex)
{
LoggerFactory
.CreateLogger(nameof(BlazoredVideo))
.LogError(ex, "Failed to register an event handler for {0}", eventName);
.LogError(ex, "Failed to register an event handler for {0}", eventName);
}
}

Expand All @@ -199,6 +199,7 @@ protected virtual void OnChange(ChangeEventArgs args)
{
videoData = JsonSerializer.Deserialize<VideoEventData>(ThisEvent, serializationOptions);
videoData.Video = this;
videoData.State ??= new VideoState();
videoData.State.Video = this;
}
catch (Exception ex)
Expand Down Expand Up @@ -304,6 +305,7 @@ protected virtual void OnChange(ChangeEventArgs args)
// Here is our catch-all event handler call!
EventFired?.Invoke(videoData);
}

bool RegisterEventFired => EventFiredEventRequired || EventFiredRequired;
bool RegisterAbort => AbortEventRequired || AbortRequired;
bool RegisterCanPlay => CanPlayEventRequired || CanPlayRequired;
Expand Down
22 changes: 22 additions & 0 deletions src/Blazored.Video/RepeatValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Blazored.Video;

/// <summary>
/// Defines methods for a <see cref="VideoQueue"/> to behave when a video source was played to its end.
/// </summary>
public enum RepeatValues
{
/// <summary>
/// Defines no repetition after the queue reached its end.
/// </summary>
NoLoop,

/// <summary>
/// Will repeat the current loaded video forever.
/// </summary>
LoopOne,

/// <summary>
/// Loops back to the first video after the last one ended.
/// </summary>
LoopAll
}
69 changes: 69 additions & 0 deletions src/Blazored.Video/VideoItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace Blazored.Video;

/// <summary>
/// Defines a Source from which the <see cref="VideoQueue"/> will schedule playback.
/// </summary>
public sealed class VideoItem : ComponentBase, IDisposable
{
public VideoItem()
{
VideoItemData = new VideoItemData();
}

/// <summary>
/// The source URI from which to playback.
/// </summary>
[Parameter]
public string Source { get; set; }

/// <summary>
/// The mime type of the <see cref="Source"/> URI. Optional.
/// </summary>
[Parameter]
public string Type { get; set; }

[CascadingParameter()]
public VideoQueue VideoQueue { get; set; }

[Parameter]
public RenderFragment ChildContent { get; set; }

public VideoItemData VideoItemData { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (ChildContent == null)
{
return;
}

builder.OpenComponent<CascadingValue<VideoItem>>(0);
builder.AddAttribute(1, nameof(CascadingValue<VideoItem>.Value), this);
builder.AddAttribute(2, nameof(CascadingValue<VideoItem>.IsFixed), true);
builder.AddAttribute(3, nameof(CascadingValue<VideoItem>.ChildContent), (RenderFragment)((cBuilder) =>
{
cBuilder.AddContent(4, ChildContent);
}));
builder.CloseComponent();
}

protected override void OnInitialized()
{
if (!string.IsNullOrWhiteSpace(Source))
{
VideoItemData.VideoSourceData.Add(new VideoSourceData(Source, Type));
}

VideoQueue.AddVideoItem(VideoItemData);
}

public void Dispose()
{
VideoQueue.VideoItems.Remove(VideoItemData);
}
}
23 changes: 23 additions & 0 deletions src/Blazored.Video/VideoItemData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;

namespace Blazored.Video;

/// <summary>
/// Contains the list of sources to be played back by a <see cref="VideoQueue"/>.
/// </summary>
public class VideoItemData
{
public VideoItemData()
{
VideoSourceData = new List<VideoSourceData>();
Id = Guid.NewGuid().ToString("N");
}

internal string Id { get; }

/// <summary>
/// The <see cref="VideoSourceData"/> that can be used to playback a source.
/// </summary>
public IList<VideoSourceData> VideoSourceData { get; set; }
}
Loading