Skip to content

Commit

Permalink
Feature/video queue (#23)
Browse files Browse the repository at this point in the history
* Added VideoQueue Control
* Added VideoQueue Example
* Added Additional Attributes bag support
* Added conditional render for additional attributes
* Fixed NREX with Additional Attributes
* Added Play method for direct playback of a single VideoSource
* Added warning ignores
* Added additional check that no prior event is overwritten
* Updated comment for delay clarification
* Added additional event types
* Added Extensive exception information
* Updated Example property access
* Updated Example
* Updated Readme

---------

Co-authored-by: JPVenson <[email protected]>
  • Loading branch information
JPVenson and JPVenson authored Dec 22, 2024
1 parent 2d6ff2c commit f7302b0
Show file tree
Hide file tree
Showing 11 changed files with 633 additions and 4 deletions.
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

0 comments on commit f7302b0

Please sign in to comment.