Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.0.0</Version>
<Version>9.0.1</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/components/BootstrapBlazor.Player/Player.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

@if (Mode == PlayerMode.Audio)
{
<audio id="@Id" playsinline controls crossorigin>
<audio id="@Id" playsinline controls crossorigin data-bb-event="@EventString">
</audio>
}
else {
<video id="@Id" playsinline controls crossorigin>
<video id="@Id" playsinline controls crossorigin data-bb-event="@EventString">
</video>
}
26 changes: 22 additions & 4 deletions src/components/BootstrapBlazor.Player/Player.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ public partial class Player
[EditorRequired]
public PlayerOptions? Options { get; set; }

private string? ClassString => CssBuilder.Default("bb-video-player")
.AddClassFromAttributes(AdditionalAttributes)
.Build();
/// <summary>
/// Gets or sets the client event callback. Default is null.
/// </summary>
[Parameter]
public Func<string, Task>? OnEvent { get; set; }

private string? EventString => OnEvent == null ? "true" : null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): Review the logic of the EventString property.

The mapping is inverted: ‘true’ is set when OnEvent is null and cleared when a callback exists. Swap these so JS events fire only when a callback is provided.


/// <summary>
/// <inheritdoc/>
Expand All @@ -39,7 +43,7 @@ protected override async Task InvokeInitAsync()
{
Options.Language ??= CultureInfo.CurrentUICulture.Name;
}
await InvokeVoidAsync("init", Id, Interop, "", Options);
await InvokeVoidAsync("init", Id, Interop, nameof(TriggerEvent), Options);
}

/// <summary>
Expand All @@ -48,4 +52,18 @@ protected override async Task InvokeInitAsync()
/// <param name="option"></param>
/// <returns></returns>
public Task Reload(PlayerOptions option) => InvokeVoidAsync("reload", Id, option);

/// <summary>
/// Trigger <see cref="OnEvent"/> event callback. Triggered by JSInterop.
/// </summary>
/// <param name="eventName"></param>
/// <returns></returns>
[JSInvokable]
public async Task TriggerEvent(string eventName)
{
if (OnEvent != null)
{
await OnEvent(eventName);
}
}
}
106 changes: 61 additions & 45 deletions src/components/BootstrapBlazor.Player/Player.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export async function init(id, invoke, method, options) {
...options
}
p.player = new Plyr(el, config);
handlerEvents(p);

if (source.sources.length === 0) {
return;
}
Expand All @@ -62,10 +64,69 @@ const initHls = (p, options) => {
setTimeout(() => hls.subtitleTrack = player.currentTrack, 50);
});
p.player = player;
handlerEvents(p);
});
}
}

export function reload(id, options) {
const p = Data.get(id);
if (p === null) {
return;
}

const { player, hls } = p;
const source = options.source.sources;
delete options.source;
if (hls) {
if (source.length > 0) {
const src = source[0].src;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const src = source[0].src;
const {src} = source[0];


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

hls.loadSource(src);
}
}
else {
player.poster = source.poster ?? options.poster;
player.source = source;
}
}

export function setPoster(id, poster) {
const p = Data.get(id);
if (p) {
const { player } = p;
player.poster = poster;
}
}

export function dispose(id) {
const p = Data.get(id);
Data.remove(id);

if (p) {
const { player } = p;
if (player) {
player.destroy();
player = null;
Comment on lines +108 to +109
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Avoid reassigning a destructured variable in dispose.

Since 'player' is a destructured const, nulling it doesn’t affect p. To clear the reference, assign null to p.player.

}
}
}

const handlerEventName = (name, p) => {
const { el, invoke, method, player } = p;
player.on(name, () => {
const fire = el.getAttribute('data-bb-event') === 'true';
if (fire) {
invoke.invokeMethodAsync(method, name);
}
});
}

const handlerEvents = p => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider inlining the event registration logic into a single loop to simplify the code.

Consider inlining the event registration logic into a single loop instead of creating two helper functions. This reduces one layer of abstraction without altering functionality. For example, instead of having separate functions:

const handlerEventName = (name, p) => {
    const { el, invoke, method, player } = p;
    player.on(name, () => {
        const fire = el.getAttribute('data-bb-event') === 'true';
        if (fire) {
            invoke.invokeMethodAsync(method, name);
        }
    });
}

const handlerEvents = p => {
    ['ready', 'play', 'pause', 'ended', 'enterfullscreen', 'exitfullscreen', 'languagechange']
    .forEach(name => {
        handlerEventName(name, p);
    });
}

You can inline the logic like this:

const handlerEvents = (p) => {
    const { el, invoke, method, player } = p;
    ['ready', 'play', 'pause', 'ended', 'enterfullscreen', 'exitfullscreen', 'languagechange']
        .forEach(name => {
            player.on(name, () => {
                if (el.getAttribute('data-bb-event') === 'true') {
                    invoke.invokeMethodAsync(method, name);
                }
            });
        });
}

This change maintains the same behavior while simplifying the code structure.

['ready', 'play', 'pause', 'ended', 'enterfullscreen', 'exitfullscreen', 'languagechange'].forEach(name => {
handlerEventName(name, p);
});
}

const setLang = (option) => {
option.i18n = {
restart: '重启',
Expand Down Expand Up @@ -112,48 +173,3 @@ const setLang = (option) => {
}
}
}

export function reload(id, options) {
const p = Data.get(id);
if (p === null) {
return;
}

const { player, hls } = p;
const source = options.source.sources;
delete options.source;
if (hls) {
if (source.length > 0) {
const src = source[0].src;
hls.loadSource(src);
}
}
else {
player.poster = source.poster ?? options.poster;
player.source = source;
}
}

export function setPoster(id, poster) {
execute(id, p => {
const { player } = p;
player.poster = poster;
});
}

const execute = (id, callback) => {
const p = Data.get(id);
if (p) {
callback(p);
}
}

export function dispose(id) {
const p = Data.get(id);
Data.remove(id);

execute(id, player => {
player.destroy();
player = null;
});
}