I am using the NAudio library to write a simple WinForms audio recorder/player. My problem is how can I tell that playback is completed? I need to close the wave stream after that.
I knew there is a PlaybackStopped event listed below:
wfr = new NAudio.Wave.WaveFileReader(this.outputFilename);
audioOutput = new DirectSoundOut();
WaveChannel32 wc = new NAudio.Wave.WaveChannel32(wfr);
audioOutput.Init(wc);
audioOutput.PlaybackStopped += new EventHandler<StoppedEventArgs>(audioOutput_PlaybackStopped);
audioOutput.Play();
But this PlaybackStopped event seems can only be triggered by calling audioOutput.stop(), does anyone know how to determine if playback is completed?
I create an open source project for this question, you can find it here: https://code.google.com/p/stack-overflow-questions/
The PlaybackStopped event is raised when you either manually stop playing, or the Read method of the IWaveProvider you are using returns 0. The issue here is that WaveChannel32 does not stop returning data when it's source stream ends, so playback never ends. The PadWithZeroes property should be set to false to fix this.
As #Mark Heath described in addition I want to add coding example of Naudio wich will play a mp3 file in Debug/Sound Folder folder and wait until it isn't finished.
Playback is completed can be checked by waveOut.PlaybackState == PlaybackState.Stopped
play_string = #"SOUND/space.mp3";
var reader = new Mp3FileReader(play_string);
var waveOut = new WaveOut(); // or WaveOutEvent()
waveOut.Init(reader);
waveOut.Play();
while (waveOut.PlaybackState != PlaybackState.Stopped) ; // Wait untill the playing isn't finished.
Here is my code in Visual Basic that works well for me to determine when the playback is done. It uses a loop to keep testing WaveOut.PlaybackState
In the loop, you will notice that a short thread sleep of 50 is used to stop the cpu running away. I picked 50 from experience, you may find another value works better for you through trial and error.
And finally, the loop allows windows.forms events so the user can click the Stop button, with the statement:
System.Windows.Forms.Application.DoEvents
In the code I have supplied below, you can see events being raised - for example:
RaiseEvent Enable_PlayButton
I have defined events to allow a parent class to enable the Play, Stop, and Record buttons:
Public Event Enable_PlayButton(Enabled As Boolean)
Public Event Enable_StopButton(Enabled As Boolean)
Public Event Enable_RecordButton(Enabled As Boolean)
Public Event Enable_SaveButton(Enabled As Boolean)
Public Event Enable_RevertButton(Enabled As Boolean)
Public Event RecordingChanged(NewRecording As Byte())
In a parent class, I use AddHandler to wire these up to method in the parent class. In the following example, I have methods such as in the first case, "EnablePlay". Likewise for the other events.
AddHandler mMicrophoneRecorder.Enable_PlayButton, AddressOf EnablePlay
AddHandler mMicrophoneRecorder.Enable_StopButton, AddressOf EnableStop
AddHandler mMicrophoneRecorder.Enable_RecordButton, AddressOf EnableRecord
AddHandler mMicrophoneRecorder.Enable_RevertButton, AddressOf EnableRevert
AddHandler mMicrophoneRecorder.RecordingChanged, AddressOf MicRecorder_RecordingChanged
In this method, there are 3 fields that are defined elsewhere:
mWaveOut_via_SoundCard - a class-level (field) of WaveOut.
mAudioFile - instantiated here but held at the class level (a field).
mWavRecordingPath - the path to the audio (wav) file being played.
These 3 fields could probably be passed in to this method as parameters if you prefer to inject them. I used fields, because that's just the way my code for this evolved.
Here then, is the code:
Public Sub A_PlayClicked()
If mWaveOut_via_SoundCard Is Nothing Then
mWaveOut_via_SoundCard = New WaveOutEvent()
End If
If mAudioFile Is Nothing AndAlso mWavRecordingPath <> "" Then
mAudioFile = New AudioFileReader(mWavRecordingPath)
mWaveOut_via_SoundCard.Init(mAudioFile)
End If
RaiseEvent Enable_StopButton(True)
mWaveOut_via_SoundCard.Play()
Dim PlayDone As Boolean = False
Dim PState As PlaybackState
Do
'if stopped or finished, mSoundCard will be nothing,
'so we test that by trying to get PlayBackState
' from the WaveOut object (mSoundCard)
If mWaveOut_via_SoundCard Is Nothing Then
PlayDone = True
Else
Try
PState = mWaveOut_via_SoundCard.PlaybackState
Catch ex As Exception
'mSoundCard is probably nothing - but
'no matter what the problem, for now we will say
'that PlayDone is true.
PlayDone = True
End Try
End If
'Okay we got the PlayState, so evaluate and
'decide whether to continue here:
If Not PlayDone Then
If PState <> PlaybackState.Stopped Then
'let the system do stuff (e.g. user might click Stop button)
System.Windows.Forms.Application.DoEvents()
'don't use all the cpu:
Threading.Thread.Sleep(50)
Else
'well it's stopped so we're done:
PlayDone = True
End If
End If
Loop Until PlayDone = True
'here we could raiseevent stopped as well
RaiseEvent Enable_PlayButton(mCanPlay)
RaiseEvent Enable_RecordButton(True)
End Sub
Related
This question already has answers here:
Run method on UI thread from another thread
(3 answers)
Closed 1 year ago.
I am using the SQLDependency class to listen for SQL notifications and the SQLDependency.OnChange event is executed on a different thread from the UI thread. My intention is to update the data in a grid view when this event is fired so SQL server database changes can instantly be reflected in the grid.
If I run my LoadData method to reload the data into the grid I get a cross-threading error:
I have created a custom class to hold all the SQLDependency related code, and there is a single instance of this class declared globally across my application. I have an event on the class that is raised inside the SQLDependency.OnChanged event. This event is then handled on various different forms so their data can be reloaded. However, because the OnChange event is raised on a different thread, I need to add logic to run first:
Delegate Sub ReloadCallback()
Private Sub LoadOnUI()
If Me.InvokeRequired Then
Dim d As ReloadCallback = New ReloadCallback(AddressOf LoadOnUI)
Me.Invoke(d)
Else
Invoke(New MethodInvoker(Sub() RefreshData()))
End If
End Sub
I would prefer to avoid having to duplicate this code and copy it to each for where my custom event is handled. This answer is pretty much exactly what I am looking for but I can't figure out how to implement the code provided.
Logic down the lines of, get the invocation list of the event and get the thread of one of the handlers, then raise the event on that thread. With my limited experience I am unable to write code to do this on my own. Any assistance would be appreciated.
Objective: Raise an event on the UI thread with no extra code repeated in the event handlers.
Solution:
Run this code on the target thread ahead of time (e.g. class constructor).
objSyncContext = SynchronizationContext.Current
Call this method RunOnUIThread(AddressOf RefreshData) from another thread to run the referenced method on the target thread.
Delegate Sub CallDelegate()
Private Sub RunOnUIThread(objEvent As CallDelegate)
If objSyncContext Is Nothing Then
objEvent()
Else
objSyncContext.Post(Sub() objEvent(), Nothing)
End If
End Sub
Here's a quick example using SynchronizationContext.
Note that it assumes you are creating the class from the main UI thread, and you store that current context from the constructor:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim d As New Duck("Bob")
AddHandler d.Quack, AddressOf duck_Quack
Label1.Text = "Waiting for Quack..."
d.RaiseThreadedQuack(3)
End Sub
Private Sub duck_Quack(source As Duck)
Label1.Text = "Quack received from: " & source.Name
End Sub
End Class
Public Class Duck
Public ReadOnly Property Name() As String
Private sc As WindowsFormsSynchronizationContext
Public Sub New(ByVal name As String)
Me.Name = name
sc = WindowsFormsSynchronizationContext.Current
End Sub
Public Event Quack(ByVal source As Duck)
Public Sub RaiseThreadedQuack(ByVal delayInSeconds As Integer)
Dim T As New Threading.Thread(AddressOf ThreadedQuack)
T.Start(delayInSeconds)
End Sub
Private Sub ThreadedQuack(ByVal O As Object)
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(O).TotalMilliseconds)
sc.Post(AddressOf RaiseUIQuack, Me)
End Sub
Private Sub RaiseUIQuack(ByVal O As Object)
RaiseEvent Quack(O)
End Sub
End Class
My problem is what event will file after i kill the Main Process?
I have no problem when killing the Sub Process. When i kill the Sub frmAdmin Process,
For eg.
This event will be fire,
Private Sub frmAdmin_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If e.CloseReason = CloseReason.TaskManagerClosing Then
'Put you desired Code inside this!
'updateLogoutStatus(Euserid)
'updateLogoutStatus(DecryptAdminUser)
MsgBox("Why are you terminating me from : TaskManager?")
End If
End Sub
But when i kill the Main Process, Which contains the sub process,
Nothing happens. No event fire.
I already searched to internet but they only kill the sub process, not Main process.
Any ideas will be a big help.
The reason why i'm doing this because if the user Kill the main process using Task Manager, I want to trigger a Function.
Here is something I use, similar to your requirement.
Execute the update Query on Form Dispose Event
Private Sub frmAdmin_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed
Try
'Update query and set flags to true / false'
Catch ex As Exception
'log exceptions (if any) to a local log file'
Finally
GC.Collect() 'Just release whatever memory your application occupied.'
End Try
End Sub
Firstly I am using VS2013 Winforms .net 4.0.
After excluding all other possibilities (from my set of possibilities) the culprit appears to be Me.close() in one specific form. After the me.close() method executes the coded-ui-test application seems to freeze and does not give any feedback about the buttons i am pressing or text i am entering. When I ask it to generate the code it goes as long as 1 hour before I decide to give up and kill the process. When I try the same test without the me.close it works as expected. Does anyone have any idea how to fix this bug in the automated ui testing? If not can you suggest any alternatives please?
Edit: This does not happen when I simply press the forms 'X' button in the top right. This is very strange.
Edit2: I have tried this in a fresh project. It is indeed me.close that causes the coded ui test application to 'freeze' such that I cannot generate the automated code and I will end up stuck at the 'please wait' loading bar.
Edit3: It appears to be specific to calling me.close in an infragistics click handler of a ultrabutton. Wow, here is example.
Designer
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class closemepls
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.UltraButton1 = New Infragistics.Win.Misc.UltraButton()
Me.SuspendLayout()
'
'UltraButton1
'
Me.UltraButton1.Location = New System.Drawing.Point(45, 47)
Me.UltraButton1.Name = "UltraButton1"
Me.UltraButton1.Size = New System.Drawing.Size(232, 157)
Me.UltraButton1.TabIndex = 0
Me.UltraButton1.Text = "UltraButton1"
'
'closemepls
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.UltraButton1)
Me.Name = "closemepls"
Me.Text = "closemepls"
Me.ResumeLayout(False)
End Sub
Friend WithEvents UltraButton1 As Infragistics.Win.Misc.UltraButton
End Class
Code
Public Class closemepls
Private Sub UltraButton1_Click(sender As Object, e As EventArgs) Handles UltraButton1.Click
Me.Close()
End Sub
End Class
If I call closemepls.showdialog() and click the button the coded ui test application freezes! Infragistics FTW.
This is the result, it does not complete even after 1 hour.
I had problems like that in WPF before.
Here's how I found it: Comment everything out then find the code that causes the problem by adding blocks back one by one.
It's something that is binded (anything though, not data and more than likely a control) not being released.
I haven't tried mixing CodedUI and VB much, but I was hoping this question may help you:
Me.Close does not work
Alone, that doesn't mean much, right? But in conjunction with how CodedUI works it may provide a clue. Remember that when you're running a test you're technically initiating a UITesting.Playback, which is a process. You may want to add something to your TestCleanup method that makes sure all the processes are terminated, like so (Keep in mind that this is for a browser test):
/// <summary>
/// Closes the test browser and ends test playback
/// </summary>
[TestCleanup]//The decorator is what makes this a method a test cleanup
public void CleanTest()
{
if (Playback.IsInitialized) //This is the important part.
{
Playback.Cleanup();
}
if (browserWindow.Exists)
{
browserWindow.Close();
}
}
This is just a shot in the dark and I may even be misunderstanding what you need, really but I'm assuming that in both your real and example areas you're closing the entire application? This may be a question for Infragistics at the end of the day. Good luck!
This might be the same issue I've experienced when using MTM, on a few machines if they were running any form of capture for the resulting test. They where trying to save it to a illegal path(Found in Event Viewer). After performing a repair on VS thus MTM to was repaired and it worked for some machines. Others only seamed to be fixed when going to update 4.
But code wise I would suggest trying to click the close button on the form itself to see if you get a different behavior.
Dim closeButton = New WinButton(YourWindow);
closeButton.SearchProperties(UITestControl.PropertyNames.Name) = "Close";
Mouse.Click(closeButton);
I'm trying to make a simple windows service.
All this service does is change the volume for one device when another device's volume is altered. It currently works as an application, and is only two Subs and a dll to function.
Basically an event handler is created on the Form Load:
AddHandler device.AudioEndpointVolume.OnVolumeNotification, AddressOf volume
Which calls the sub:
Private Sub volume(data As CoreAudioApi.AudioVolumeNotificationData)
If Me.InvokeRequired Then
Me.Invoke(New AudioEndpointVolumeNotificationDelegate(AddressOf volume), data)
Else
loopback.AudioEndpointVolume.MasterVolumeLevelScalar = data.MasterVolume
loopback.AudioEndpointVolume.Mute = data.Muted
End If
End Sub
Now in a windows service I can't use invoke, and when I remove it the error The function evaluation requires all threads to run is thrown.
What I believe is happening is that the control data and loopback (which are the controls of the audio Device accessed via the CoreAudioApi.dll) is not resolving without the invoke, but I don't know how to make them resolve properly.
Determine changes of master audio volume using NAudio.dll from: https://github.com/SjB/NAudio
Imports NAudio.CoreAudioApi
Public Class Form1
Private enumer As MMDeviceEnumerator = New MMDeviceEnumerator()
Private dev As MMDevice = enumer.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
AddHandler dev.AudioEndpointVolume.OnVolumeNotification, AddressOf AudioEndpointVolume_OnVolumeNotification
End Sub
Private Sub AudioEndpointVolume_OnVolumeNotification(ByVal data As AudioVolumeNotificationData)
' This shows data.MasterVolume, you can do whatever you want here
Me.Text = CInt(data.MasterVolume.ToString() * 100) & " Mute=" & data.Muted
End Sub
End Class
If I remove the if around the invoke statement so it just has the line:
Me.Invoke(New AudioEndpointVolumeNotificationDelegate(AddressOf volume), data)
followed by the other two lines it throws the error:
"'Invoke' is not a member of 'VolumeSet.VolumeSet'"
So I don't think invoke can be used inside a windows service at all.
But thanks for the idea!
I am using a WebBrowser control in VB.NET and calling the Print() method. I am printing out using a PDF printer and when Print() is called it is not immediately kicked off (it waits until it has completed running code for the entire Sub or block.
I need to make sure the file I am printing too is complete and continue process with this file, therefore, I would like to print on demand and get some status of when the operation is complete. I have tried usign printDocument and process without luck.
Anyone have any ideas?
Check out the PrintTemplateTeardown event of the underlying unmanaged WebBrowser object. Sometimes that event gets fired multiple times but hopefully this will point you in the right direction. You need to add a reference to Microsoft Internet Controls.
Private Sub Print()
AddHandler DirectCast(WebBrowser1.ActiveXInstance, SHDocVw.WebBrowser).PrintTemplateTeardown, AddressOf PrintDone
WebBrowser1.Print()
End Sub
Private Sub PrintDone(ByVal obj As Object)
Trace.WriteLine("printed")
RemoveHandler DirectCast(WebBrowser1.ActiveXInstance, SHDocVw.WebBrowser).PrintTemplateTeardown, AddressOf PrintDone
End Sub
Your best bet is to get a handle on your 'printjobscollection' for your default printer and ensure that the jobcount = 0
like this in vb.net:
Dim intprint As Integer = Nothing
retry2:
intprint = GetPrintJobsCollection(printerinuse)
If Not intprint = 0 Then
System.Threading.Thread.Sleep(1000)
GoTo retry2
End If
'do what you want to do after print completes here