mediaEnded event doesn't always fire - c#

I am using a design for playing wav files one after another. This design is based on mediaEnded event and works fine. When trying to imply this design on very short wav files, the mediaEnded event isn't always raised.
Is their a solution for this problem?
Thanks.

I implemented a workaround using a timer to periodically check for two situations:
Video Playback Position has not moved since last check - indicates the video is stuck.
Video Playback Position is beyond the end of the video - indicates the video is playing but usually just a black screen will be displayed.
If either condition exists force the next vide to start playing.
Note that this is an extract of code from a class file.
namespace XXXX
{
public partial class VideoWindow : Window
{
private DispatcherTimer mediaPositionTimer; // We check the play position of the video each time this fires to see if it has hung
double _lastPosition = -1;
int _video_count;
int videoDuration;
DateTime lastVideoStartTime;
public VideoWindow()
{
adMediaElement.LoadedBehavior = MediaState.Manual;
adMediaElement.MediaEnded += adMediaElement_MediaEnded;
adMediaElement.MediaOpened += adMediaElement_MediaOpened;
adMediaElement.MediaFailed += adMediaElement_MediaFailed;
}
// Increment the counter every time we open a video
void adMediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
Log("Media opened");
_video_count++;
}
// If we have a failure then just start the next video
void adMediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
Log("Media failed");
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
PlayNextMedia();
}));
}
// When a video ends start the next one
private void adMediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
Log("MediaEnded called");
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
PlayNextMedia();
}));
}
// Stops and Closes any existing video
// Increments the file pointer index
// Switches to display ads if no more videos
// else
// Starts the video playing
private void PlayNextMedia()
{
// Log(String.Format("PlayNextMedia called"));
//Close the existing file and stop the timer
EndVideo2();
adMediaElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
adMediaElement.Stop();
adMediaElement.Close();
}));
currentMediaFileIndex++;
if (currentMediaFileIndex > (mediaFileCount - 1))
{
Log(String.Format(" switching to Ads, currentMediaFileIndex = {0}", currentMediaFileIndex));
// Now setup and then run static adds for 10 minutes
currentMediaFileIndex = 0;
StartAds();
}
else
{
Log(String.Format(" switching media, index = {0}", currentMediaFileIndex));
adMediaElement.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
StartVideo2();
}));
}
}
// Stops the mediaPositionTimer, must be called in conjunction with a admediaElement.Pause() or Stop()
private void EndVideo2()
{
// Log("EndVideo2() called");
_is_playing = false;
// Stop the timer
if (mediaPositionTimer != null)
{
mediaPositionTimer.Stop();
}
}
// Load the media file
// Set the volume
// Set the lastVideoStartTime variable
private void StartVideo2()
{
// Log("StartVideo2() called");
loadMediaFile(currentMediaFileIndex);
adMediaElement.Volume = Properties.StationSettings.Default.VolumeMedia;
lastVideoStartTime = DateTime.Now; // Record the time we started
// Stop the timer if it exists, otherwise create a new one
if (mediaPositionTimer == null)
{
mediaPositionTimer = new DispatcherTimer();
// Set up the timer
mediaPositionTimer.Interval = TimeSpan.FromSeconds(10);
mediaPositionTimer.Tick += new EventHandler(positionTimerTick);
}
else
{
mediaPositionTimer.Stop();
}
// Start it running
adMediaElement.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
mediaPositionTimer.Start();
_is_playing = true;
adMediaElement.Play();
}));
}
private void RestartVideo2()
{
//Log("Restart the video");
adMediaElement.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
mediaPositionTimer.Start();
_is_playing = true;
adMediaElement.Play();
}));
}
// Check the playback position of the mediaElement has moved since the last check,
// if not then the video has hung
// Also check if the playback position exceeds the video duration,
// if so it has also hung
// If video has hung then force the next one to start and report a Warning
private void positionTimerTick(object sender, EventArgs e)
{
double duration;
double currentPosition = adMediaElement.Position.TotalSeconds;
if (adMediaElement.NaturalDuration.HasTimeSpan)
{
duration = adMediaElement.NaturalDuration.TimeSpan.TotalSeconds;
}
else
{
duration = 5 * 60; // Default to 5 minutes if video does not report a duration
}
if ((currentPosition == lastPosition) || currentPosition > (duration + 30))
{
// do something
//Log("*** Video position has exceed the end of the media or video playback position has not moved ***");
if (_is_playing)
{
String logString = String.Format("*** Video {0} has frozen ({1}c:{2}l:{3}d)so forcing the next one to start ***", mediaFiles[currentMediaFileIndex], currentPosition, lastPosition, duration);
Log(logString);
PlayNextMedia();
// Send a message indicating we had to do this
mainWindow.SendHeartbeat(MainWindow.STATUS_WARNING, logString);
}
}
lastPosition = currentPosition;
}
}
}

Related

How do I store text file into an array and then display line by line on demand (through key binding)

I'm a teacher with a very limited programming background struggling with C# to make some videos for my students who are pretty much anxious and depressed during the pandemic. I'm using GTA5 as my platform because it is easy to customize, has amazing locations, hundreds of potential characters, and supports voice as well (I'm using Amazon Polly). Needlessly to say I'm stripping out all the violence and swearing.
The roadblock I'm hitting, and there isn't any support to speak of on any GTA forums or Mods forums, is how to display lines of text on the screen on demand. I've managed to do this hardcoded, but I would prefer to read this from a text file and display line by line, not by a timer, but on demand with a single key binding, ideally with the ability to go back and forth (but line by line).
A friend of mine who works for a AAA gaming company, doesn't know the GTA5 environment but said the solution would be to read the lines into an array. Unfortunately I don't have that programming knowledge, so I'm stuck at this point. Here is my code. Right now it will only display the last line of a test text file. Code is put together from Microsoft documentation and random GTA forum posts. Again, I can do this manually through multiple hardcoded lines of text, each with a key bind, but that's totally impractical. Need text file, one keybinding and a way go line by line (and ideally backwards)
using System;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Math;
using GTA.Native;
namespace TextDrawing
{
public class TextDraw : Script
{
public static string TextString { get; set; }
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
string[] lines = System.IO.File.ReadAllLines
(#"C:\Users\UserName\test.txt");
foreach (string line in lines)
{
TextString = line; //"Lesson 1 Topics Today. \n\n Part 1. Drawing Text on Screen \n\n Part 2. Customizations" + TextString2;
DrawText();
}
}
}
}
}
There are many ways to do this. But a few key code changes should make it work.
Create two class-level variables, an array to keep all the read files and an int index to keep track of your current index.
Create a function to read and invoke that in the constructor (ideally it should not be read in the constructor but rather triggered by external user action. But let's keep it simple for now).
In the key press event, update the string and increase the index. Your code is currently not working because you are iterating through the entire array and assigning text to a single variable. So only the last value is remains on screen.
public class TextDraw : Script
{
public static string TextString { get; set; }
public string[] AllLines { get; set; }
public int currentIndex = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
ReadAllLines(); ///Ideally not here. But should work still.
}
private void ReadAllLines()
{
AllLines = System.IO.File.ReadAllLines (#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
if (currentIndex >= 0 && currentIndex < AllLines.Length)
{
TextString = AllLines[currentIndex];
}
else
{
TextString = "The End";
}
currentIndex++;
DrawText();
}
}
}
I'm not familiar with GTA scripting, but maybe create a member variable: int index = 0; Then use that to figure out which line to print in DrawText():
namespace TextDrawing
{
public class TextDraw : Script
{
// The lines of the text file
string [] lines;
// The index of the next line to print
int index = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
// Read the file here
lines = System.IO.File.ReadAllLines(#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (0 == lines.Length || Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// Get next line
string textString = lines[index];
var Text4Screen = new GTA.UI.TextElement(textString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000);
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
// When Keys.NumPad7 is pressed, show next line
if (e.KeyCode == Keys.NumPad7)
{
if (index < lines.Length - 1) index++;
}
// When Keys.NumPad8 is pressed, show previous line
else if (e.KeyCode == Keys.NumPad8)
{
if (index > 0) index--;
}
}
}
}
Updated to use onTick and go backwards. Apparently the onTick is needed to continually redraw the text. Being unfamiliar with the GTA API, I did not realize that.

How to change a picturebox once a gif ends

I have a PictueBox and I have some dice, I would like to play an animation for the "rolling" of the dice, I did a .gif with the dice, but after the dice stop rolling, I want the actual dice number that I got, I have a random funcion that handles that.
My question is, I press the "Roll Dice" button, it plays the animation and after the animation ends I should set int the picturebox the dice that actually came. but it immediately chnages to the dice number that actually came, skipping the animation;
This is how it works:
dice1.Image = Resources.DiceAnimation; //Here the gif is called to be played
int x = rollDice(); //Here I roll the dice
switch (x){
case 1: dice.Image = resources.diceFace1; //Image set depending on x
break
case 2: //etc...
}
There might be two things needed to do that.
Firstly, you may need to ensure that your PictureBox receives a gif image and it knows it. To do this, please check this answer and this answer. The posts have code to show GifImage frame by frame:
public class GifImage
{
private Image gifImage;
private FrameDimension dimension;
private int frameCount;
private int currentFrame = -1;
private bool reverse;
private int step = 1;
public GifImage(string path)
{
gifImage = Image.FromFile(path);
//initialize
dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
//gets the GUID
//total frames in the animation
frameCount = gifImage.GetFrameCount(dimension);
}
public bool ReverseAtEnd {
//whether the gif should play backwards when it reaches the end
get { return reverse; }
set { reverse = value; }
}
public Image GetNextFrame()
{
currentFrame += step;
//if the animation reaches a boundary...
if (currentFrame >= frameCount || currentFrame < 1) {
if (reverse) {
step *= -1;
//...reverse the count
//apply it
currentFrame += step;
}
else {
currentFrame = 0;
//...or start over
}
}
return GetFrame(currentFrame);
}
public Image GetFrame(int index)
{
gifImage.SelectActiveFrame(dimension, index);
//find the frame
return (Image)gifImage.Clone();
//return a copy of it
}
}
Use it like this (note that you need a Timer object):
private GifImage gifImage = null;
private string filePath = #"C:\Users\Jeremy\Desktop\ExampleAnimation.gif";
public Form1()
{
InitializeComponent();
//a) Normal way
//pictureBox1.Image = Image.FromFile(filePath);
//b) We control the animation
gifImage = new GifImage(filePath);
gifImage.ReverseAtEnd = false; //dont reverse at end
}
private void button1_Click(object sender, EventArgs e)
{
//Start the time/animation
timer1.Enabled = true;
}
//The event that is animating the Frames
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Image = gifImage.GetNextFrame();
}
Secondly, to know how long you want to run your GIF image, you may need to Get Frame Duration of GIF image like this:
double delayIn10Ms; //declare somewhere
//Initialize on your form load
PropertyItem item = img.GetPropertyItem (0x5100); // FrameDelay in libgdiplus
// Time is in 1/100th of a second
delayIn10Ms = (item.Value [0] + item.Value [1] * 256) * 10;
Then use the delayIn10Ms time plus, probably, a little bit more time to stop your timer. You may also want to check when was the last time your timer Ticks and store it. If it exceeds the given delay time, then you should stop your timer and start it again on dice roll, after image assignment in your switch case.
DateTime currentTick = DateTime.Min;
DateTime startTick = DateTime.Min;
private void timer1_Tick(object sender, EventArgs e)
{
currentTick = DateTime.Now;
if ((currentTick - startTick).TotalSeconds / 100 < delayIn10Ms)
pictureBox1.Image = gifImage.GetNextFrame();
else
timer1.Stop(); //stop the timer
}
//And somewhere else you have
timer1.Start(); //to start the timer
int x = rollDice(); //Here I roll the dice
switch (x){
case 1: dice.Image = resources.diceFace1; //Image set depending on x
break
case 2: //etc...
}
You can make a timer with the Interval property set to the length of the animation and set it's Tag to 0 and in the timer write the code:
if(timer.Tag == "0")
timer.Tag == "1";
else if(timer.Tag == "1")
{
int x = rollDice();
switch (x)
{
case 1: dice.Image = resources.diceFace1; break;
case 2: //etc...
}
timer.Tag == "0";
timer.Stop();
}

Issues with hand Clapping gesture

This my code for clapping hand gesture ,i use the result to set a robotic arm end effector to a 3d location for every clap)
i want the event handler to trigger just once for every clap gesture,
and reset for another clap gesture. but when i clap and my hands are close together, my event handler keeps firing!! please how do i correct this issue. could i use a reset method or something
[hand clap code]
float previousDistance = 0.0f;
private void MatchClappingGesture(Skeleton skeleton)
{
if (skeleton == null)
{
return;
}
if (skeleton.Joints[JointType.WristRight].TrackingState == JointTrackingState.Tracked && skeleton.Joints[JointType.WristLeft].TrackingState == JointTrackingState.Tracked)
{
float currentDistance = GetJointDistance(skeleton.Joints[JointType.WristRight], skeleton.Joints[JointType.WristLeft]);
{
if (currentDistance < 0.1f && previousDistance > 0.1f )
{
if (this.GestureRecognized != null)
{
this.GestureRecognized(this, new GestureEventArgs(RecognitionResult.Success));
previousDate = DateTime.Now;
}
}
previousDistance = currentDistance;
}
}
}
this is where i call the event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
kinect = KinectSensor.KinectSensors[0];
kinect.Start();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Could not find Kinect Camera: " + ex.Message);
}
kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
kinect.ColorStream.Enable(ColorImageFormat.RgbResolution1280x960Fps12);
kinect.SkeletonStream.Enable(new TransformSmoothParameters()
{
Correction = 0.5f,
JitterRadius = 0.05f,
MaxDeviationRadius = 0.05f,
Prediction = 0.5f,
Smoothing = 0.5f
});
kinect.AllFramesReady += Kinect_SkeletonAllFramesReady;
recognitionEngine = new GestureRecognitionEngine();
armEngine = new ArmControllerEngine();
recognitionEngine.GestureRecognized += new EventHandler<GestureEventArgs>(recognitionEngine_GestureRecognized);
}
event handler fires here
void recognitionEngine_GestureRecognized(object sender,
GestureEventArgs e)
{
//armEngine.setArm(raMove,port,servoId);
MessageBox.Show("HI");
}
the message box prints multiple time instead of just once!!please help
You must bear in mind that Kinect's measurements have a degree of inaccuracy. Also, in certain cases (like when the hands are together!!) that inaccuracy increases greatly, and the detected joint positions start jumping all over the place. Try it with the SDK's provided skeleton viewer and you'll see. This is often mitigated by using smoothing algorithms, but in your case that might not be the most adequate solution. Try increasing the distance you are using to detect the gesture, give it some 20cm or so (btw, what does your GetJointDistance returns? Meters? Then give it 0.2)
void wait(int a)
{
for (int i = 0; i < a; i++) ;
}
int count = 0;
void recognitionEngine_GestureRecognized(object sender, GestureEventArgs e)
{
wait(3);
clapShow();
}
//clap method
void clapShow()
{
count += 1;
if (count == 1)
{
MessageBox.Show("Gesture Capture Within >> " + time.Millisecond.ToString() + " milli Seconds");
}
count = 0 ;
}
}
I called the message box as a method every 3millsecs wait(3) so that slows down the the event handler.
Then set the count to "1" before a gesture then back to "0"after every clap gesture and it works pretty well!!
Note!.... The wait(X) method i created a simple loop that counts down to the value X. It can be any value based on the level of sensitivity level one desires. Thanks jose!!
i hope this helps someone..

Detect current volume level while a file is playing using Naudio

So I have
IWavePlayer waveOutDevice;
WaveStream mainOutputStream;
WaveChannel32 volumeStream;
private WaveStream CreateInputStream(string fileName)
{
WaveChannel32 inputStream;
if (fileName.EndsWith(".mp3"))
{
WaveStream mp3Reader = new Mp3FileReader(fileName);
inputStream = new WaveChannel32(mp3Reader);
}
else
{
throw new InvalidOperationException("Unsupported extension");
}
volumeStream = inputStream;
return volumeStream;
}
private void Stop()
{
if (waveOutDevice != null)
{
waveOutDevice.Stop();
}
if (mainOutputStream != null)
{
// this one really closes the file and ACM conversion
volumeStream.Close();
volumeStream = null;
// this one does the metering stream
mainOutputStream.Close();
mainOutputStream = null;
}
if (waveOutDevice != null)
{
waveOutDevice.Dispose();
waveOutDevice = null;
}
}
private void Play(string was)
{
waveOutDevice = new WaveOut();
mainOutputStream = CreateInputStream(was);
waveOutDevice.Init(mainOutputStream);
waveOutDevice.Play();
}
private void Form1_Load(object sender, EventArgs e)
{
Play(#"E:\Eigene Audiodateien\Musik\Alben\Pur\Abenteuerland\ - - .mp3");
}
private void button1_Click(object sender, EventArgs e)
{
Stop();
}
There is a Stop-Button ( button1 ), which stops playback. When the form is loaded, the file is played. While the file is playing I want to get the current volume of the file by running a function. So what does a function like this has to look like at "...."?
private int currentVolumeLevel(...some suitable parameters...)
{
int currentVolumeLevelValue = 0;
//....
return currentVolumeLevelValue;
}
I am not talking about the volume level you can adjust with windows' sound controls. I am talking about the currently played sound file's volume at this very position it is playing right now, based on something like a byte[] array.
The NAudioDemo shows how to do this. Look in AudioPlaybackPanel.cs at how a MeteringSampleProvider is added to the playback pipeline. MeteringSampleProvider will periodically raise StreamVolume events telling you the maximum sample value you have received in the last 100ms (this is configurable). You will need to decide whether you want to place the MeteringSampleProvider before or after any software volume adjustment (for waveform drawing it is usually before, and for volume metering it is usually after)
Here's a working WindowsForms demo, writing the stream volume to the Console:
var player = new WaveOut();
var file = new AudioFileReader(#"test.mp3");
var meter = new MeteringSampleProvider(file);
meter.StreamVolume += (s,e) => Console.WriteLine("{0} - {1}", e.MaxSampleValues[0],e.MaxSampleValues[1]);
player.Init(new SampleToWaveProvider(meter));
var form = new Form();
form.Load += (s,e) => player.Play();
form.FormClosed += (s,e) => player.Dispose();
form.ShowDialog();

C# Set duration and play sounds stored in an arraylist consecutively

I have the following Play method for playing musical notes stored in an arraylist:
public void Play(Duration d){
int duration = 1;
if (d == Duration.SemiBreve)
{
duration = 16;
}
else if (d == Duration.DotMin)
{
duration = 10;
}
else if (d == Duration.minim)
{
duration = 8;
}
else if (d == Duration.Crotchet)
{
duration = 4;
}
else if (d == Duration.Quaver)
{
duration = 2;
}
else if (d == Duration.SemiQuaver)
{
duration = 1;
}
player = new SoundPlayer();
player.SoundLocation = "pathToLocation";
//set timer
time = new Timer();
time.Tick += new EventHandler(clockTick);
time.Interval = duration * 150;
player.Play();
time.Start();
}
When I call the method with a button:
private void PlayButton_Click(object sender, EventArgs e)
{
//Loops through the collection and plays each note one after the other
foreach (MusicNote music in this.staff.Notes)
{
music.Play(music.Dur)
}
}
Only the last note gets played. With PlaySync() all the notes get played but the duration isn't recognized. I also tried using threads like so:
foreach (MusicNote music in this.staff.Notes)
{
Thread t = new Thread(playMethod=>music.Play(music.Dur));
t.Start();
t.Join();
}
But it doesn't work either. Any suggestions as to how I can get the files to play consecutively like with PlaySync but using their set duration?
You don't wait for the timer anywhere. So in practice all notes get played almost simultaneously, causing just the last one to be effectively audible.
UPDATE: Using the System.Timers.Timer class the way to go is registering a handler for the Elapsed event and playing the notes one-by-one in the event handler. This way you avoid freezing the UI.
Another option is playing in a background thread and the using Thread.Sleep to wait. Here you will have to make sure the thread is stopped according to the state of the UI (i.e. the use closes the current dialog etc.).
In either case to avoid race conditions you will have to solve concurrent access to staff.Notes or make a copy of it for the sake of playing it.

Categories