I am working on a project that extracts YouTube videos' audio and saves them to your computer.
To do this, I used a library from GitHub called YouTubeExtractor.
I am using a backgroundworker in order to make the UI usable while the file is being downloaded. This is the code I have so far.
public partial class MainWindow : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
}
private void downloadButton_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
string link;
double percentage;
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
link = videoURL.Text;
}));
/*
* Get the available video formats.
* We'll work with them in the video and audio download examples.
*/
IEnumerable<VideoInfo> videoInfos = DownloadUrlResolver.GetDownloadUrls(link);
/*
* We want the first extractable video with the highest audio quality.
*/
VideoInfo video = videoInfos
.Where(info => info.CanExtractAudio)
.OrderByDescending(info => info.AudioBitrate)
.First();
/*
* If the video has a decrypted signature, decipher it
*/
if (video.RequiresDecryption)
{
DownloadUrlResolver.DecryptDownloadUrl(video);
}
/*
* Create the audio downloader.
* The first argument is the video where the audio should be extracted from.
* The second argument is the path to save the audio file.
*/
var audioDownloader = new AudioDownloader(video, System.IO.Path.Combine("C:/Downloads", video.Title + video.AudioExtension));
// Register the progress events. We treat the download progress as 85% of the progress and the extraction progress only as 15% of the progress,
// because the download will take much longer than the audio extraction.
audioDownloader.DownloadProgressChanged += (send, args) => Console.WriteLine(args.ProgressPercentage * 0.85);
audioDownloader.AudioExtractionProgressChanged += (send, args) => Console.WriteLine(85 + args.ProgressPercentage * 0.15);
/*
* Execute the audio downloader.
* For GUI applications note, that this method runs synchronously.
*/
audioDownloader.Execute();
}
}
}
The problem I have is that I want to display this
audioDownloader.DownloadProgressChanged += (send, args) => Console.WriteLine(args.ProgressPercentage * 0.85);
audioDownloader.AudioExtractionProgressChanged += (send, args) => Console.WriteLine(85 + args.ProgressPercentage * 0.15);
In a UI element like a label or a progressbar instead of Console.WriteLine
Whenever I do label1.Text = (85 + args.ProgressPercentage * 0.15); It throws me a an error like
" The calling thread cannot access this object because a different thread owns it."
I know you can do solve this with a delegate, I need a clear instruction on how so.
Thank you.
Here's a modern approach for doing this using Tasks and async / await keywords
Plus the usage of Dispatcher.BeginInvoke for updating your UI.
Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using YoutubeExtractor;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow() {
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e) {
string videoUrl = #"https://www.youtube.com/watch?v=5aXsrYI3S6g";
await DownloadVideoAsync(videoUrl);
}
private Task DownloadVideoAsync(string url) {
return Task.Run(() => {
IEnumerable<VideoInfo> videoInfos = DownloadUrlResolver.GetDownloadUrls(url);
VideoInfo videoInfo = videoInfos.FirstOrDefault();
if (videoInfo != null) {
if (videoInfo.RequiresDecryption) {
DownloadUrlResolver.DecryptDownloadUrl(videoInfo);
}
string savePath =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
Path.ChangeExtension("myVideo", videoInfo.VideoExtension));
var downloader = new VideoDownloader(videoInfo, savePath);
downloader.DownloadProgressChanged += downloader_DownloadProgressChanged;
downloader.Execute();
}
});
}
private void downloader_DownloadProgressChanged(object sender, ProgressEventArgs e) {
Dispatcher.BeginInvoke((Action) (() => {
double progressPercentage = e.ProgressPercentage;
ProgressBar1.Value = progressPercentage;
TextBox1.Text = string.Format("{0:F} %", progressPercentage);
}));
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<StackPanel>
<Button Click="Button_Click" Content="Download" />
<ProgressBar x:Name="ProgressBar1"
Height="20"
Maximum="100" />
<TextBox x:Name="TextBox1" />
</StackPanel>
</Grid>
</Window>
Related
I have a long running WCF service and a client that consumes it via WPF. Am using a Progress Bar to notify the client of the percentage completion for a particular process (a method in WCF: I need to be able to display the percentage based on the looping counter in the service)
I have used Background Worker to display progress percentage but it does not display the progress correctly. (displays just 0 and 100 not the in between values) Everything works fine in DEBUG mode but not in RELEASE mode! (Progress bar is updated sequentially in DEBUG mode)
I tried using callbacks/wsDualHttpBinding but have some difficulty in getting this incorporated for all clients. So, had to drop this option.
working on async/await
I have googled quite a few links but nothing helps with my problem.
Please guide me on how to get the current/running value from a method that is not complete yet from a WCF service so I could populate the progress bar percentage based on this value. (in between values)
P.S: WCF service uses wsHttpBinding
sample code below:
public Progress()
{
// Start the BackgroundWorker.
myBGWorker.WorkerReportsProgress = true;
myBGWorker.WorkerSupportsCancellation = false;
myBGWorker.DoWork += myBGWorker_DoWork;
myBGWorker.ProgressChanged += myBGWorker_ProgressChanged;
}
public void ShowProgress()
{
myBGWorker.RunWorkerAsync();
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
// fetches a static value from the service
string value = _client.Progress();
int p=0;
for (int i = 1; i <= 100; i++)
{
// Report progress.
p = Convert.ToInt32(_client.Progress());
_logger.Debug("Progress5:" + p.ToString());
myBGWorker.ReportProgress(p, i);
}
}
private void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(delegate
{
progressBar1.Value = e.ProgressPercentage;
}), DispatcherPriority.ContextIdle);
}
Just to understand how it could be implemented. This is part of my working code.
.xaml file:
<ProgressBar x:Name="ProgressBarCompare" HorizontalAlignment="Left" Height="20" Margin="10,157,0,0" VerticalAlignment="Top" Width="321"/>
Function with process:
private async void btnCompare_Click(object sender, RoutedEventArgs e)
{
ProgressBarCompare.Value = 0;
lblCompare.Content = "";
List<string> list1= (List<string>)Application.Current.Properties["list1"];
List<string> list2= (List<string>)Application.Current.Properties["list2"];
List<Result> output = new List<Result>();
List<Result> passed = new List<Result>();
int topCount = emailList.Count;
int currentItem = 0;
int topBound = topCount - 1;
while (currentItem < topCount)
{
var hash = await CheckOperation(list1[currentItem]); // this line perform progress bar to be filled
var result = list2.Contains(hash);
//some operations
if (Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) < 51)
{
Style style = this.FindResource("LabelTemplateNotFilled") as Style;
lblCompare.Style = style;
}
else
{
Style style = this.FindResource("LabelTemplateFilled") as Style;
lblCompare.Style = style;
}
ProgressBarCompare.Value = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound));
lblCompare.Content = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) + "%";
currentItem++;
}
lblCompare.Content = "COMPLETE";
}
and core function to that:
private async Task<string> CheckOperation(string input)
{
var result = "";
await Task.Run(() =>
{
//perform some code
});
return result;
}
I am no sure what I am doing wrong to load a gif in the window. An animated gif would be best if possible even if it only looped once (don't need to control it).
// WPF
<Image Name="ImageViewer1" Height="100" Width="100" Margin="340,178,0,0" />
// Load Gif
public MainWindow()
{
InitializeComponent();
ImageViewer1.Source = new Image(#"giphy.gif");
}
ImageViewer not have supported for simple use. I recomended use MediaElement.
It's easier and supported gif for animation.
<MediaElement x:Name="gif" MediaEnded="gif_MediaEnded" UnloadedBehavior="Manual" Source=#"giphy.gif" LoadedBehavior="Play" Stretch="None"/>
And for looping:
private void gif_MediaEnded(object sender, RoutedEventArgs e)
{
gif.Position = new TimeSpan(0, 0, 1);
gif.Play();
}
private void myMedia_MediaEnded(object sender, RoutedEventArgs e)
{
myMedia.Position = new TimeSpan(0, 0, 1);
myMedia.Play();
}
private void myMedia_MediaOpened(object sender, RoutedEventArgs e)
{
if (myMedia.Stretch != Stretch.Uniform)
{
myMedia.Stretch = Stretch.Uniform;
}
else
{
myMedia.Stretch = Stretch.Uniform;
}
MediaElement me = sender as MediaElement;
ScaleTransform newT = new ScaleTransform();
newT.ScaleX = (16.0 / 9.0) * me.NaturalVideoHeight / me.NaturalVideoWidth;
newT.ScaleY = 1;
me.RenderTransform = newT;
}
I'm struggling with a longlistselector and item realized event. The problem I'm facing is that the longlistselector does not show all elements.
The code I'm doing is not using MVVM (I know that I should use, but in this scenario I can't...it was heritage code).
This is what I have:
XAML:
<Scrollviewer>
<stackpanel>
<phone:LongListSelector Margin="0,15,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" x:Name="LBhistory" LayoutMode="List"
BorderThickness="0,15,0,0" >
<phone:LongListSelector Margin="0,15,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" x:Name="LBDevices" LayoutMode="List" BorderThickness="0,15,0,0" >
<phone:LongListSelector Margin="0,15,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" x:Name="LBfiles" LayoutMode="List" BorderThickness="0,15,0,0" >
</stackpanel>
</ScrollViewer>
CS file:
private bool _isLoadingAllFile;
private int _pageNumber = 0;
private ObservableCollection<PhotoObject> allFiles = new ObservableCollection<PhotoObject>();
public BackupPivotPage()
{
....
this.Loaded += PivotPage_Loaded;
}
private void PivotPage_Loaded(object sender, RoutedEventArgs e)
{
LBfiles.ItemsSource = allFiles;
LBfiles.ItemRealized += LBfiles_ItemRealized;
searchImages(_pageNumber++);
}
private void searchImages(int p)
{
_isLoadingAllFile = true;
var x = dbAllFiles.Skip(p * GlobalSettings.PageSize.myPictures)
.Take(GlobalSettings.PageSize.myPictures);
foreach (var toAddObject in x)
{
this.allFiles.Add(toAddObject);
}
_isLoadingAllFile = false;
}
void LBfiles_ItemRealized(object sender, ItemRealizationEventArgs e)
{
try
{
if (!_isLoadingAllFile && LBfiles.ItemsSource != null &&
LBfiles.ItemsSource.Count >= Constants.offsetKnob)
{
if (e.ItemKind == LongListSelectorItemKind.Item)
{
if ((e.Container.Content as PhotoObject)
.Equals(LBfiles.ItemsSource[LBfiles.ItemsSource.Count - Constants.offsetKnob]))
{
searchImages(this._pageNumber++);
}
}
}
}
catch (Exception e1)
{
}
}
Right now my problem is that I know that allFiles has 96 elements, but only 67 are shown and the rest appear as white...any idea why?
EDIT
I've update with the scrollviewer...because I've 3 longlistselectors in the same page...and only this last one doesn't show all the items.
The problem seems to be around the time with which the data is loaded (or the thread). The ItemRealised event happens on a Background Thread therefore isn't able to update the User interface. In the example reference below they perform a similar operation to yours but retrieve the data using Deployment.Current.Dispatcher. This is used to do the work on the UI thread.
Try something similar to the following:
Try
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
var x = dbAllFiles.Skip(p * GlobalSettings.PageSize.myPictures)
.Take(GlobalSettings.PageSize.myPictures);
foreach (var toAddObject in x)
{
this.allFiles.Add(toAddObject);
}
IsLoading = false;
});
}
catch (Exception e)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Network error occured " + e.Message);
});
}
TwitterSearch - Windows Phone 8 LongListSelector Infinite Scrolling Sample
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();
i have a single video (duration: 3 seconds) and i need to create 2 states
1- the video should always reach the second 1.5 and play it from the start.
TimeSpan ts = new TimeSpan(0, 0, 0, 1, 500);
TimeSpan ts_Start = new TimeSpan(0, 0, 0, 0, 0);
if (mediaElement.position == ts)
mediaElement.position = ts_Start; //doesnt work this block code
2- when i press a button, the video should play the full video (3 seconds). (simple flag, boolean)
so my question is, how do i know when the mediaelement.position = 1.5 seconds ??.... i thought of a method such as playing or something like that.
If you get the MediaElement's Clock property, you could attach onto the CurrentTimeInvalidated event and watch for the time to hit 1.5 seconds. The event has a lot of precision (i.e. it gets raised VERY often) so you don't want to do too much in response to the event unless you have to.
i resolved the problem... :) :) ....
i decide make me own application with many ideas that had taken of other forums.
My solution was easier than i planned, i used 2 videos, 2 mediaElements, a mediaEnded event and boolean variable to chage the video....
and works perfectly! Solution are here ------> (Solution, and coments)
in my app, i didn't have to use properties like clocks, TimeLines, DispatcherTimer, or any event like a CurrentTimeInvalidate, i just used the MediaEnded event and a boolean variable. :) no more. i have 2 videos (1,5 seconds and 3 seconds). when MediaEnded(media 1,5 seconds) mediaElement1,5sec.Position = TimeSpam.Zero; and MediElement3sec.Position = TimeSpam.Zero, and when i clicked the button, i just evaluated the variable (boolean) and play complet video of 3 seconds.
however, the source code are here: MainWindow.xaml
<Window x:Class="wpf_TestVideos.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="371" Width="525" Loaded="Window_Loaded">
<Grid>
<MediaElement Height="268" HorizontalAlignment="Left" Margin="141,12,0,0" Name="mediaElement15sec" VerticalAlignment="Top" Width="237" MediaEnded="mediaElement15sec_MediaEnded" />
<MediaElement Height="268" HorizontalAlignment="Left" Margin="142,12,0,0" Name="mediaElement3sec" VerticalAlignment="Top" Width="236" />
<Button Content="Load" Height="34" HorizontalAlignment="Left" Margin="12,286,0,0" Name="btLoad" VerticalAlignment="Top" Width="73" Click="btLoad_Click" />
<Button Content="Inicio Juego" Height="23" HorizontalAlignment="Left" Margin="128,286,0,0" Name="btStart" VerticalAlignment="Top" Width="86" Click="btStart_Click" />
<Button Content=""Reconoce Gesto"" Height="23" HorizontalAlignment="Left" Margin="285,286,0,0" Name="btGesture" VerticalAlignment="Top" Width="108" Click="btGesture_Click" />
</Grid>
MainWindow.xaml.cs:
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Media.Animation;
using System.Threading;
namespace wpf_TestVideos
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
string VideoLocation = System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath);
string sFileName = "";
string sFileName2 = "";
bool bVideoLoop = true;
TranslateTransform trans = new TranslateTransform();
private void btLoad_Click(object sender, RoutedEventArgs e)
{
mediaElement15sec.LoadedBehavior = MediaState.Manual;
mediaElement3sec.LoadedBehavior = MediaState.Manual;
btGesture.IsEnabled = true;
btStart.IsEnabled = true;
btLoad.IsEnabled = false;
DirectoryInfo df = new DirectoryInfo(VideoLocation);
if (df.Exists)
{
sFileName = VideoLocation + #"\Krown_test_loop.mov";
mediaElement15sec.Source = new Uri(sFileName);
mediaElement15sec.Stretch = Stretch.Fill;
sFileName2 = VideoLocation + #"\Krown_test_7.mov";
mediaElement3sec.Source = new Uri(sFileName2);
mediaElement3sec.Stretch = Stretch.Fill;
}
else
{
System.Windows.Forms.MessageBox.Show("No se puede cargar el video", "TestAll");
}
}
private void btStart_Click(object sender, RoutedEventArgs e)
{
mediaElement15sec.Position = TimeSpan.Zero;
mediaElement3sec.Position = TimeSpan.Zero;
mediaElement15sec.Play();
mediaElement3sec.Play();
bVideoLoop = true;
//VisualStateManager.GoToState(mediaElement15sec, "Bring1,5ToFront", true);
}
private void mediaElement15sec_MediaEnded(object sender, RoutedEventArgs e)
{
if (bVideoLoop)
{
mediaElement15sec.Position = TimeSpan.Zero;
mediaElement3sec.Position = TimeSpan.Zero;
}
}
private void btGesture_Click(object sender, RoutedEventArgs e)
{
bVideoLoop = false;
//Animacion_Opacidad(bVideoLoop);
//VisualStateManager.GoToState(mediaElement3sec, "Bring300ToFront", true);
}
private void Animacion_Opacidad(bool bLoop)
{
mediaElement15sec.RenderTransform = trans;
if (!bLoop)
{
DoubleAnimation anim1 = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(1));
trans.BeginAnimation(OpacityProperty, anim1);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
btGesture.IsEnabled = false;
btStart.IsEnabled = false;
btLoad.IsEnabled = true;
}
}
}
The accepted solution seems to be more a workaround than a solution.. what in case once you will need to use not 1.5 but another time, eg. 2.5 seconds? will you have to change the videos? The solution could be using a DistpatcherTimer:
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1.5); // your time
timer.Tick += timer_Tick;
timer.Start();
mePlayer.Play(); // run timer and player at same time
When the timer_Tick is reached just set the position to zero and call Play() again:
void timer_Tick(object sender, EventArgs e)
{
mePlayer.Position = new TimeSpan(0, 0, 0, 0);
mePlayer.Play();
}
And when clicking the second button, detach the timer (... can be attached later when necessary):
timer.Tick -= timer_Tick;