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
19 changes: 10 additions & 9 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,31 @@
<PackageVersion Include="Azure.Storage.Blobs" Version="12.25.1" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.13.0" />
<PackageVersion Include="Markdig.Signed" Version="0.42.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.9" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Emoji" Version="4.12.1" />
<PackageVersion Include="PublishSPAforGitHubPages.Build" Version="3.0.1" />
<PackageVersion Include="PublishSPAforGitHubPages.Build" Version="3.0.3" />
<!-- Test dependencies -->
<PackageVersion Include="bunit" Version="1.40.0" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="SkiaSharp" Version="3.119.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageVersion Include="Svg.Skia" Version="3.0.6" />
<PackageVersion Include="Svg.Skia" Version="3.2.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4">
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Moq" Version="4.20.72" />
<!-- Shared dependencies -->
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.0" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.12.1" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.12.1" />
Expand Down
2 changes: 0 additions & 2 deletions FluentUI.Blazor.Community.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
ο»Ώ
Microsoft Visual Studio Solution File, Format Version 12.00

# Visual Studio Version 18
VisualStudioVersion = 18.0.11018.127

MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Expand Down
1 change: 1 addition & 0 deletions examples/demo/FluentUI.Demo.Client/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script type="text/javascript" src="_content/FluentUI.Demo.Shared/js/highlight-extensions.js"></script>
<script type="text/javascript" src="_content/FluentUI.Demo.Shared/js/blob.js"></script>
</body>

</html>
2,247 changes: 2,246 additions & 1 deletion examples/demo/FluentUI.Demo.Shared/FluentUI.Blazor.Community.Components.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.FluentUI.AspNetCore.Components;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;

namespace FluentUI.Demo.Shared.Layout;
Expand Down Expand Up @@ -98,6 +97,11 @@ public DemoNavProvider()
)
]
),
new NavLink(
href: "audio",
icon: new Icons.Regular.Size20.SoundWaveCircle(),
title: "Audio"
),
new NavLink(
href: "cookies",
icon: new Icons.Regular.Size20.Cookies(),
Expand Down
51 changes: 51 additions & 0 deletions examples/demo/FluentUI.Demo.Shared/Pages/Audio/AudioPage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
ο»Ώ@page "/audio"
@using FluentUI.Demo.Shared.Pages.Audio.Examples

<h1>Audio</h1>

<p>
The <code>FluentCxAudio</code> component allows you to play audio files in your Blazor application.
</p>

<h2 id="example">Examples</h2>

<DemoSection Component="typeof(AudioDefault)" Title="Default">
<Description>
The FluentCxAudio component can plays many audio files.
You can play, pause, move to next or previous track, show the playlist, manage volume, download track.
To download a track, if the track is on an outside website, you need to configure CORS.

The FluentCxAudio has 4 views :

<ul>
<li>Default</li>
<li>Compact</li>
<li>Minimal</li>
<li>Floating</li>
</ul>

The visualizer has 8 modes for now :
<ul>
<li>Spectrum</li>
<li>Waveform</li>
<li>Spatial</li>
<li>Vortex</li>
<li>Particles</li>
<li>RadialWaveform</li>
<li>Tunnel</li>
<li>Constellation</li>
<li>Fractal</li>
</ul>
</Description>
</DemoSection>

<h2 id="documentation">Documentation</h2>

<ApiDocumentation Component="typeof(FluentCxAudio)" />
<ApiDocumentation Component="typeof(AudioTrackItem)" />
<ApiDocumentation Component="typeof(MinimalPlayer)" />
<ApiDocumentation Component="typeof(TrackInfo)" />
<ApiDocumentation Component="typeof(AudioTitleScroller)" />
<ApiDocumentation Component="typeof(CompactPlayer)" />
<ApiDocumentation Component="typeof(AudioVisualizer)" />
<ApiDocumentation Component="typeof(AudioControls)" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
ο»Ώ@using Microsoft.AspNetCore.StaticFiles
@using Microsoft.JSInterop
@implements IDisposable
@inject IAudioMetadataProvider AudioMetadataProvider

<FluentStack Orientation="Orientation.Vertical">
<FluentMessageBar Intent="MessageIntent.Warning" AllowDismiss=false>
The HTML Audio Element has an issue when the user uses the seek bar.
</FluentMessageBar>

<FluentMessageBar Intent="MessageIntent.Warning" AllowDismiss=false>
The metadata are extracted using TagLib#. This lib is very strict, so if your audio file is not well formed, it will throw an exception,
and an empty metadata will be used instead. Be sure that your audio files are well formed. For example, if you download an mp3 file which was
encoded with m4a codec, this will not work. But if you rename it with m4a, this will work.
</FluentMessageBar>

<div style="height: 600px; margin-top: 5px">
<FluentStack Orientation="Orientation.Vertical">
<FluentStack>
<FluentSelect Items="@(Enum.GetValues<AudioPlayerView>())"
TOption="AudioPlayerView"
Label="View"
@bind-SelectedOption=_view />

<FluentSelect Items="@(Enum.GetValues<VisualizerMode>())"
TOption="VisualizerMode"
Label="Visualizer"
@bind-SelectedOption=_mode />
</FluentStack>

<FluentStack>
<FluentInputFile Id="my-file-uploader"
Mode="InputFileMode.SaveToTemporaryFolder"
Multiple="true"
MaximumFileCount="10"
MaximumFileSize="@(10*1024*1024)"
Accept="audio/*"
@bind-ProgressPercent="@ProgressPercent"
OnCompleted="@OnCompletedAsync"
Style="height: 300px;">
<ChildContent>
<label for="my-file-uploader">
<FluentIcon Value="@(new @Icons.Regular.Size24.ArrowUpload())" />
</label>

<div>
Drag files here you wish to upload,
or <label for="my-file-uploader">browse</label>
for them<span style="color: red;">*</span>.
<br />
<em>Maximum of 10 files allowed.</em>
</div>
</ChildContent>
</FluentInputFile>

<FluentCxAudio VisualizerMode="@_mode" View="@_view">
@foreach (var item in _audioFiles)
{
<AudioTrackItem Source="@GetPath(item.Value)"
Metadata="@_metadataCache[item.Key]" />
}
</FluentCxAudio>
</FluentStack>
</FluentStack>
</div>

</FluentStack>

@code {
private VisualizerMode _mode;
private AudioPlayerView _view = AudioPlayerView.Default;
private FluentCxAudio? _audio;

int ProgressPercent = 0;
private readonly Dictionary<string, string> _audioFiles = [];
private readonly List<FluentInputFileEventArgs> _files = [];
private readonly Dictionary<string, AudioMetadata> _metadataCache = [];

[Inject]
private IJSRuntime Runtime { get; set; } = null!;

private static string GetPath(string path)
{
if (path.StartsWith("blob"))
{
return path;
}

var index = path.IndexOf("uploads");

if (index >= 0)
{
return path[(index - 1)..].Replace("\\", "/");
}

return path.Replace(Directory.GetCurrentDirectory(), string.Empty)
.Replace("wwwroot", string.Empty)
.Replace("\\", "/")
.Substring(2);
}

private async Task OnCompletedAsync(IEnumerable<FluentInputFileEventArgs> files)
{
foreach (var file in files)
{
var newFileName = $"RenamedFile_{Guid.NewGuid()}{Path.GetExtension(file.Name)}";
var filePath = newFileName;

if (System.OperatingSystem.IsBrowser())
{
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(file.Name, out var contentType))
{
contentType = "application/octet-stream";
}

filePath = await Runtime.InvokeAsync<string>("getBlobUrl", contentType, File.ReadAllBytes(file.LocalFile!.FullName));
}
else
{
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", newFileName);
var directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory!);
}

await using var stream = File.OpenRead(file.LocalFile!.FullName);
await using var fileStream = File.Create(filePath);
await stream.CopyToAsync(fileStream);
}

if (!_audioFiles.ContainsKey(file.Name))
{
_audioFiles.Add(file.Name, filePath);
}

if (!_metadataCache.ContainsKey(file.Name))
{
using var ms = new MemoryStream(File.ReadAllBytes(file.LocalFile.FullName));
var metadata = AudioMetadataProvider.GetFromStream(file.Name, ms);
_metadataCache.Add(file.Name, metadata);
}

if (!_files.Contains(file))
{
_files.Add(file);
}
}

await Task.Delay(500);
ProgressPercent = 0;
}

public void Dispose()
{
try
{
foreach (var file in _audioFiles)
{
if (File.Exists(file.Value))
{
File.Delete(file.Value);
}
}

foreach(var file in _files)
{
if (file.LocalFile is not null && File.Exists(file.LocalFile.FullName))
{
File.Delete(file.LocalFile.FullName);
}
}
}
catch { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
@code {
private readonly RenderFragment<DashboardItem> renderCard = context => __builder =>
{
<FluentCard>
<FluentCard MinimalStyle>
<div class="fluent-tile-grid-item-header fluent-card-header">
<div class="fluent-tile-grid-item-title">
@context.Header
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions examples/demo/FluentUI.Demo.Shared/wwwroot/js/blob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
window.getBlobUrl = (contentType, byteArray) => {
const blob = new Blob([new Uint8Array(byteArray)], { type: contentType });
return URL.createObjectURL(blob);
};
15 changes: 15 additions & 0 deletions src/Community.Components/Components/Audio/Audio.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ο»Ώ@namespace FluentUI.Blazor.Community.Components
@inherits FluentComponentBase

<audio id="@Id"
preload="metadata"
style="display: none;"
@key=@Id
@ref="Element"></audio>

@code {
public Audio()
{
Id = $"audio-{Identifier.NewId()}";
}
}
55 changes: 55 additions & 0 deletions src/Community.Components/Components/Audio/AudioControls.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
ο»Ώ@namespace FluentUI.Blazor.Community.Components
@inherits FluentComponentBase

<FluentStack Id="@Id"
VerticalAlignment=VerticalAlignment.Center
HorizontalAlignment="HorizontalAlignment.Center"
HorizontalGap="30">
<PlaylistButton Label="@Labels.PlaylistLabel" OnPlaylist="@OnPlaylistToggled" />

<FluentSpacer />

<ShuffleButton ShuffleOnLabel="@Labels.ShuffleOnLabel"
ShuffleOffLabel=@Labels.ShuffleOffLabel
OnShuffleChanged="@OnShuffleChanged" />

<PreviousButton Label="@Labels.PreviousLabel"
IsDisabled=@IsPreviousDisabled
OnPrevious="@OnPrevious" />

<PlayOrPauseButton @ref=_playOrPauseButton
IsDisabled="@IsPlayOrPauseDisabled"
PlayLabel="@Labels.PlayLabel"
PauseLabel="@Labels.PauseLabel"
OnPlayChanged="@OnPlayPauseToggled" />

<StopButton OnStop="@OnStop"
Label="@Labels.StopLabel"
IsDisabled=@IsStopDisabled />

<NextButton IsDisabled=@IsNextDisabled
OnNext="@OnNext"
Label="@Labels.NextLabel" />

<RepeatButton PlaylistLoopLabel="@Labels.PlaylistLoopLabel"
PlaylistOnceLabel="@Labels.PlaylistOnceLabel"
SingleLoopLabel="@Labels.SingleLoopLabel"
SingleOnceLabel="@Labels.SingleOnceLabel"
OnRepeatModeChanged="@OnRepeatModeChanged" />

<VolumeButton Label="@Labels.VolumeLabel" OnVolumeChanged="@OnVolumeChanged" />

<FluentSpacer />

@if (IsDownloadVisible)
{
<DownloadButton IsDisabled="@IsDownloadDisabled"
Label="@Labels.DownloadLabel"
OnDownload="@OnDownload" />
}

<PropertiesButton IsDisabled="@IsPropertiesDisabled"
Label="@Labels.PropertiesLabel"
OnProperties="@OnProperties" />

</FluentStack>
Loading
Loading