beaverlyhillsstudios / wavesurfer-blazor-wrapper Goto Github PK
View Code? Open in Web Editor NEW.NET6 Core wrapper around JS library: wavesurfer.js by katspaugh (https://wavesurfer-js.org/)
License: BSD 3-Clause "New" or "Revised" License
.NET6 Core wrapper around JS library: wavesurfer.js by katspaugh (https://wavesurfer-js.org/)
License: BSD 3-Clause "New" or "Revised" License
Hello, this is great/awesome, I picked this over howler ๐ฅ , can you please update to core 7, also I noticed you have separate timeline and regions for content.
I have a list of songs for an online album, where we want to sing karaoke, but when I am trying to do lyrics (expand on hover, based on timestamp, and its confusing since it sits across two or three wavesurfer plugins (regions, timeline2, hover), but my data sits in the same C# song models, not sure how to do both timestamp, lyrics synced with the displayed time stamp on hover, while displaying play rewind and custom buttons like share.
thanks.
Hi guys, great wrapper, was not looking forward to JS Interop to use Wavesurfer. Are there any plans to implement the Markers plugin per https://wavesurfer-js.org/plugins/markers.html ? That would really make my day :) Unless it's there, and I just can't see it....
When audio is playing and reaches the end, an exception is thrown: Uncaught (in promise) Error: System.ArgumentException: The type 'WavesurferPlayer' does not contain a public invokable method with [JSInvokableAttribute("OnWavesurferFinish")].
This exception appears to trigger from wavesurfer-user.js
line 63. The JS interop is calling back to the "OnWavesurferPause" invokable method, but there is no method in WavesurferPlayer.razor that has [JSInvokable("OnWavesurferFinish")]
attached to it, which calls the OnFinish
EventCallback.
I think just adding this to WavesurferPlayer.razor
would possibly work:
[JSInvokable]
public async Task OnWavesurferFinish()
{
await OnFinish.InvokeAsync();
}
I could add this if you want. If that's ok with you, how would you prefer that be done? I could create a branch, add the changes and make a PR. Let me know if that's helpful or if you'd rather look into this on your own. Thank you!
I was wondering if support for adding regions could be implemented. Wavesurfer.js has a few methods in the regions plugin that are not present on this wrapper. https://wavesurfer-js.org/plugins/regions.html
Specifically, there's no way to add or clear regions. There are callbacks implemented for when regions are added and changed, but no direct methods on the object that allow you to add regions.
My specific use-case would be something like this:
<WavesurferPlayer @ref="this.audioPlayer" />
@code{
private WavesurferPlayer? audioPlayer;
private async Task OnButtonClick()
{
await this.audioPlayer?.AddRegion(
id: "region1",
start: 3f,
end: 6f,
loop: false,
drag: false,
resize: false,
color: "rgba(1, 0.5, 0.5, 1)",
minLength: 3f,
maxLength: 6f);
}
}
Let me know if I can help with implementing this or if this functionality already exists and I'm just missing it. Thanks!
Hi guys!
Thanks for your sharing!
Any chance to have this portet to the latest version of wavesurfer.js (I think it is 7.1.1)
Hi can you please help me with trying to get Peaks in a json format please. I have the below code in javascript I just need it to work in c#
<script type="text/javascript" src="js/jquery-3.6.0.min.js"></script> <script type="text/javascript" src="https://unpkg.com/wavesurfer.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($){
"use strict"
var dataArr = [],
processArr = [],
len,
counter,
peakType = 'pcm',
exportData,
loader = $('#loader'),
output = $('#output'),
exportLink = $('#exportLink'),
pcmLength,
pcmAccuracy,
waveform = $('#waveform'),
imageCreateCheck,
wavesurfer
$('input[name=peakType]').on('change',function() {
if(this.checked){
peakType = this.value;
}
$('.waveform-data-field').hide()
if(peakType == 'pcm'){
$('.waveform-data-field').show()
}
}).change();
function createWs(){
console.log('createWs')
if(wavesurfer){
wavesurfer.empty();
wavesurfer.cancelAjax();
wavesurfer.destroy()
}
wavesurfer = WaveSurfer.create({
container: waveform[0],
backend: 'MediaElement',/*!important*/
});
wavesurfer.on('waveform-ready', function() {
console.log('waveform-ready')
if(peakType == 'pcm'){
var pcm = wavesurfer.exportPCM(pcmLength, pcmAccuracy, true);
pcm.then(function(val) {
if(Array.isArray(val)){
val = val.toString();
}else{
if(val.charAt(0) == '[')val = val.substr(1);
if(val.charAt(val.length-1) == ']')val = val.substr(0, val.length-1);
}
if(peakType == 'pcm'){
output.append('<br>Done creating peak: ' + dataArr[counter].peakName);
exportData.push({name: dataArr[counter].peakName, pcm: val})
getPeaks();
}else if(peakType == 'peak'){
writePeaks(val, dataArr[counter].peakName);
}
})
}
});
}
var audio_input = $("#audio_input").change(function(e) {
pcmLength = $('#waveform-length').val() || 100
pcmAccuracy = $('#waveform-accuracy').val() || 100
createWs()
audio_input.prop('disabled',true);
loader.show();
output.show().html('');
exportLink.hide();
exportData = [];
dataArr = [];
len = this.files.length;
var i, fn;
for(i=0; i < len; i++){
fn = this.files[i].name;
//fn = fn.substr(0,fn.lastIndexOf('.'))
processArr.push({file:this.files[i], peakName: fn});
}
getFiles();
});
function getFiles() {
var data = processArr.shift();
var title;
var fileReader = new FileReader();
fileReader.onload = function(e){
dataArr.push(data);
dataArr[dataArr.length-1].url = e.target.result;
if(processArr.length){
getFiles();
}else{
output.append('Done reading all file inputs<br>Started peak creation<br>');
counter = -1;
len = dataArr.length;
getPeaks();
}
}
fileReader.onerror = function(e){
console.log("fileReader failed", e.name + ": " + e.message);
if(processArr.length){
getFiles();
}else{
output.append('Done reading all file inputs<br>Started peak creation<br>');
counter = -1;
len = dataArr.length;
getPeaks();
}
}
fileReader.readAsDataURL(data.file);
}
function getPeaks() {
counter++;
if(counter == len){
output.append('<br><br>Done creating all peaks!');
dataArr = null;
loader.hide();
audio_input.prop('disabled', false);
if(peakType == 'pcm'){
//Get the file contents
var txtFile = "test.txt";
var file = new File([""], txtFile);
var str = JSON.stringify(exportData, null, " ");
//Save the file contents as a DataURI
var dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(str);
var date = new Date().toLocaleString(),
file_name = 'peaks-' + date
exportLink.attr('download', file_name)
//Write it as the href for the link
exportLink.attr('href', dataUri).show();
}
audio_input.val('')
}else{
wavesurfer.empty();
var url = dataArr[counter].url
if(window.location.protocol == 'https:'){
var wurl = url.replace('http://','https://');
}else{
var wurl = url.replace('https://','http://');
}
wavesurfer.load(wurl);
}
}
});
function isEmpty(str) {
return str.replace(/^\s+|\s+$/g, '').length == 0;
}
</script>
If we use multiple Wavesurfer Players on a page, only the last Wavesurfer Player will work properly.
for example:
<WavesurferPlayer ShowDefaultToolbar Url="/file/corp/eXun10005/rec/20231024/1698107752864.mp3"></WavesurferPlayer>
<WavesurferPlayer ShowDefaultToolbar Url="/file/corp/eXun10005/rec/20231024/1698110477390.mp3"></WavesurferPlayer>
<WavesurferPlayer ShowDefaultToolbar Url="/file/corp/eXun10005/rec/20231024/1698108695731.mp3"></WavesurferPlayer>
When using the WavesurferPlayer component in multiple Razor pages, the same phenomenon occurs: only the last loaded WavesurferPlayer can display normally.
Hey, thanks for making this wrapper. I ran into an issue trying to use the OnVolume parameter on the WavesurferPlayer component. It throws an exception:
Uncaught (in promise) Error: System.Text.Json.JsonException: The JSON value could not be converted to System.Int32. Path: $ | LineNumber: 0 | BytePositionInLine: 3.
---> System.FormatException: Either the JSON value is not in a supported format, or is out of bounds for an Int32.
at System.Text.Json.Utf8JsonReader.GetInt32()
at System.Text.Json.Serialization.Converters.Int32Converter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
at System.Text.Json.Serialization.JsonConverter`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, Int32& value)
at System.Text.Json.Serialization.JsonConverter`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
--- End of inner exception stack trace ---
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
at System.Text.Json.Serialization.JsonConverter`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.JsonConverter`1[[System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore[Object](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.Read[Object](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.ParseArguments(JSRuntime jsRuntime, String methodIdentifier, String arguments, Type[] parameterTypes)
at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.InvokeSynchronously(JSRuntime jsRuntime, DotNetInvocationInfo& callInfo, IDotNetObjectReference objectReference, String argsJson)
at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, String argsJson)
I think it's throwing this because the volume is supposed to be a float
type, but the OnVolume parameter is EventCallback<int>
. I think the JS interop is having an issue when calling back to C# with the value. This is just my suspicion.
This should be a minimal reproduction. Due to CORS issues, I don't have a URL to use in the URL parameter so you'll need your own.
<WavesurferPlayer @ref="this.audioPlayer"
Url="PUT YOUR AUDIO FILE HERE"
OnVolume="this.OnVolumeChanged"/>
<button @onclick="SetVolume">Change Volume</button>
@code{
private WavesurferPlayer? audioPlayer = null;
private void OnVolumeChanged(int newVol)
{
Console.WriteLine($"New volume = {newVol}");
}
private async Task SetVolume()
{
if (this.audioPlayer != null)
{
Random rand = new();
await this.audioPlayer.SetVolume(rand.NextSingle());
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.