How to Save Record Video in SharpAvi - c#

I would like to record screen video then save as Mp4,Webm or any video format.
I am using SharpAvi to record video,The code below is recording video but I am unable to save after stop ,Please help Here is my code.
namespace RecordScreenvideo
{
// Used to Configure the Recorder
public class RecorderParams
{
public RecorderParams(string filename, int FrameRate, FourCC Encoder, int Quality)
{
FileName = filename;
FramesPerSecond = FrameRate;
Codec = Encoder;
this.Quality = Quality;
Height = (int)SystemParameters.PrimaryScreenHeight;
Width = (int)SystemParameters.PrimaryScreenWidth;
}
string FileName;
public int FramesPerSecond, Quality;
FourCC Codec;
public int Height { get; private set; }
public int Width { get; private set; }
public AviWriter CreateAviWriter()
{
return new AviWriter(FileName)
{
FramesPerSecond = FramesPerSecond,
EmitIndex1 = true,
};
}
public IAviVideoStream CreateVideoStream(AviWriter writer)
{
// Select encoder type based on FOURCC of codec
if (Codec == KnownFourCCs.Codecs.Uncompressed) return writer.AddUncompressedVideoStream(Width, Height);
else if (Codec == KnownFourCCs.Codecs.MotionJpeg) return writer.AddMotionJpegVideoStream(Width, Height, Quality);
else
{
return writer.AddMpeg4VideoStream(Width, Height, (double)writer.FramesPerSecond,
// It seems that all tested MPEG-4 VfW codecs ignore the quality affecting parameters passed through VfW API
// They only respect the settings from their own configuration dialogs, and Mpeg4VideoEncoder currently has no support for this
quality: Quality,
codec: Codec,
// Most of VfW codecs expect single-threaded use, so we wrap this encoder to special wrapper
// Thus all calls to the encoder (including its instantiation) will be invoked on a single thread although encoding (and writing) is performed asynchronously
forceSingleThreadedAccess: true);
}
}
}
public class Recorder : IDisposable
{
#region Fields
AviWriter writer;
RecorderParams Params;
IAviVideoStream videoStream;
Thread screenThread;
ManualResetEvent stopThread = new ManualResetEvent(false);
#endregion
public Recorder(RecorderParams Params)
{
this.Params = Params;
// Create AVI writer and specify FPS
writer = Params.CreateAviWriter();
// Create video stream
videoStream = Params.CreateVideoStream(writer);
// Set only name. Other properties were when creating stream,
// either explicitly by arguments or implicitly by the encoder used
videoStream.Name = "Captura";
screenThread = new Thread(RecordScreen)
{
Name = typeof(Recorder).Name + ".RecordScreen",
IsBackground = true
};
screenThread.Start();
}
public void Dispose()
{
stopThread.Set();
screenThread.Join();
// Close writer: the remaining data is written to a file and file is closed
writer.Close();
stopThread.Dispose();
}
void RecordScreen()
{
var frameInterval = TimeSpan.FromSeconds(1 / (double)writer.FramesPerSecond);
var buffer = new byte[Params.Width * Params.Height * 4];
Task videoWriteTask = null;
var timeTillNextFrame = TimeSpan.Zero;
while (!stopThread.WaitOne(timeTillNextFrame))
{
var timestamp = DateTime.Now;
Screenshot(buffer);
// Wait for the previous frame is written
videoWriteTask?.Wait();
// Start asynchronous (encoding and) writing of the new frame
videoWriteTask = videoStream.WriteFrameAsync(true, buffer, 0, buffer.Length);
timeTillNextFrame = timestamp + frameInterval - DateTime.Now;
if (timeTillNextFrame < TimeSpan.Zero)
timeTillNextFrame = TimeSpan.Zero;
}
// Wait for the last frame is written
videoWriteTask?.Wait();
}
public void Screenshot(byte[] Buffer)
{
using (var BMP = new Bitmap(Params.Width, Params.Height))
{
using (var g = Graphics.FromImage(BMP))
{
g.CopyFromScreen(Point.Empty, Point.Empty, new Size(Params.Width, Params.Height), CopyPixelOperation.SourceCopy);
g.Flush();
var bits = BMP.LockBits(new Rectangle(0, 0, Params.Width, Params.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
Marshal.Copy(bits.Scan0, Buffer, 0, Buffer.Length);
BMP.UnlockBits(bits);
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
FourCC selectedCodec = KnownFourCCs.Codecs.MotionJpeg;
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);
Recorder record = new Recorder(recparams);
}
}
}

This is very simple to save output as mp4 or any format as you can see the method`receiving file name as first argument
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);`
So you need to just pass your output file path as
RecorderParams recparams = new RecorderParams("D:\\recordvideo.mp4",49,selectedCodec,100);`

You just need to call the dispose method
so after you start recording you either need to hard-code how much time you want it to record or make it after you press any key, here is an example of main that will start record when you press any key then it will stop recording after you press any key.
private void button1_Click(object sender, EventArgs e)
{
FourCC selectedCodec = KnownFourCCs.Codecs.MotionJpeg;
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);
Console.WriteLine("Press any key to start recording ");
Console.ReadKey();
Recorder record = new Recorder(recparams);
Console.WriteLine("Press any key to stop recording ");
Console.ReadKey();
record.Dispose();
}

Related

class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback result is always null

I have converted my old StartActivityForResult code to the new RegisterForActivityResult as StartActivityForResult is depreciated in the android API 29 and higher. My new code works perfectly, except that result in class ActivityResultCallback is always null. I need to be able to know when the user cancels the taking of the picture when they hit the back button, otherwise the app crashes (if a previous picture doesn't already exist) or a previously taken picture is processed again. My code (showing only the relevant code):
public class Photo : AppCompatActivity
{
public ImageView MyImageView;
public ImageButton camerabutton;
public bool PictureTaken = false;
private CurrentSubjectInfo MySubjectInfo;
private ActivityResultCallback _activityResultCallback;
private ActivityResultLauncher _activityResultLauncher;
private Uri uri;
protected override void OnCreate(Bundle bundle)
{
try
{
_activityResultCallback = new ActivityResultCallback();
_activityResultCallback.OnActivityResultCalled += ActivityResultCallback_ActivityResultCalled;
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.TakePicture(), _activityResultCallback);
RequestedOrientation = Android.Content.PM.ScreenOrientation.Portrait;
base.OnCreate(bundle);
SetContentView(Resource.Layout.Photo);
PictureTaken = false;
MySubjectInfo = new CurrentSubjectInfo("", "", "", "", "");
// retrieve subject information from previous activity
MySubjectInfo = Mybundle.GetParcelable("MySubjectInfo", Java.Lang.Class.FromType(typeof(CurrentSubjectInfo))) as CurrentSubjectInfo;
ImageButton camerabutton = FindViewById<ImageButton>(Resource.Id.button1);
MyImageView = FindViewById<ImageView>(Resource.Id.imageView1);
camerabutton.Click += (sender, evt) =>
{
var cameraispresent = CheckCameraHardware();
if (cameraispresent)
{
try
{
// get directory where pictures are stored
App._dir = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
// build file name
MySubjectInfo.Name = MySubjectInfo.Name.Replace(",", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(" ", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(".", "");
var filename = MySubjectInfo.Name + ".jpg";
App._file = new File(App._dir, String.Format(filename, Guid.NewGuid()));
uri = FileProvider.GetUriForFile(this, this.ApplicationContext.PackageName + ".provider",App._file);
// launch camera activity
_activityResultLauncher.Launch(uri);
}
catch (Exception e)
{
// trap error and log it
};
}
};
}
}
Boolean CheckCameraHardware()
{
Android.Content.PM.PackageManager pm = PackageManager;
if (pm.HasSystemFeature(Android.Content.PM.PackageManager.FeatureCamera))
{
// this device has a camera
return true;
}
else
{
// no camera on this device
return false;
}
}
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
// result is always null, so I don't check it
try
{
// Because the resulting bitmap is rotated and too large, I set original orientation and resize
// suffice it to say it works perfectly so I won't post the code here
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
// save the resized bitmap, overwriting original file
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
// set the imageview to the resulting bitmap
MyImageView.SetImageBitmap (App.bitmap);
// cleanup
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
catch (Exception ex)
{
PictureTaken = false;
// trap and log error
}
}
}
This is the ActivityResultCallback class:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult = result as ActivityResult;
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
As stated all this code executes perfectly with no errors except that Java.Lang.Object result is always null. I need to know when the user cancels the camera activity, I would assume that Java.Lang.Object result would indicate cancelled but I get nothing. What am I missing?
Ok, after some playing around I noticed that the parameter Java.Lang.Object result in the ActivityResultCallback class was either true or false depending on what the user did with the camera. So I changed the class to:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult;
if ((bool)result)
{
activityResult = new ActivityResult((int)Result.Ok, null);
} else
{
activityResult = new ActivityResult((int)Result.Canceled, null);
}
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
In the ActivityResultCallback_ActivityResultCalled function, I modified it to this:
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
try
{
if (result.ResultCode == (int)Result.Ok)
{
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
MyImageView.SetImageBitmap(App.bitmap);
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
}
catch (Exception ex)
{
// trap and log error
}
}
activityResult apparently has, at a minimum, two parameters, the result and data. I already had what I needed for data so I set that to null and cast Result.Ok or Result.Cancelled to int depending on whether result was true or false. I still don't totally understand how to use the new ActivityForResult API, but this works for me and I'm running with it.

Streaming SharpDX Bitmap from one device to another by converting to byte array

I'm trying to capture a bitmap from desktop and then stream that to another device on the local network via socket connection.
I have a working example of this using gdi that sends a bitmap via socket to the client device and renders it to a picture box, but this solution is pretty inefficient as I'm sending unchanged bitmaps and it requires a lot of cpu processing on the client.
I'll include this example as a reference point
public class Serial
public static byte[] BitmapToBytes(Bitmap bitmap) {
using(MemoryStream ms = new MemoryStream()) {
using(BinaryWriter bw = new BinaryWriter(ms)) {
using(MemoryStream msb = new MemoryStream()) {
bitmap.Save(msb, ImageFormat.Jpeg);
byte[] bitmapBytes = msb.ToArray();
bw.Write(bitmapBytes.Length);
bw.Write(bitmapBytes);
return ms.ToArray();
}
}
}
}
public static Bitmap BytesToBitmap(byte[] bytes) {
using(MemoryStream ms = new MemoryStream(bytes)) {
using(BinaryReader br = new BinaryReader(ms)) {
int len = br.ReadInt32();
byte[] bitmapBytes = br.ReadBytes(len);
if(len == bitmapBytes.Length) {
using(MemoryStream msb = new MemoryStream(bitmapBytes)) {
return new Bitmap(msb);
}
} else {
return null;
}
}
}
}
}
public class Send {
private ClientInfo Instance;
public Send(ClientInfo instance) {
Instance = instance;
}
public void Bitmap(Bitmap bitmap) {
if(!Instanced) { return; }
Instance.SendMessage((uint)Code.Bitmap, Serial.BitmapToBytes(bitmap));
}
}
public class Server { //Same code for Client
public event EventHandler<BitmapEventArgs> BitmapReceived = delegate { };
//Socket connection setup code stripped as it's not relevant
private void Data_Received(ClientInfo c, uint code, byte[] bytes, int len) {
switch((Code)code) {
case Code.BitmapReceived:
BitmapReceived(this, new BitmapEventArgs(Serial.BytesToBitmap(bytes)));
break;
}
}
}
public partial class Form1 : Form {
private Client Client;
private Server Server;
private Send Send;
private void StartSocket() {
new Thread(() => {
StopSocket();
Client = new Client();
Client.ClientConnected += Client_Connected;
Client.ClientDisconnected += Client_Disconnected;
Client.BitmapReceived += Bitmap_Received;
if(!Client.Start(Config.Connection.IP, Config.Connection.Port)) {
Client = null;
Server = new Server();
Server.ClientConnected += Client_Connected;
Server.ClientDisconnected += Client_Disconnected;
Server.BitmapReceived += Bitmap_Received;
Server.Start(Config.Connection.Port);
}
}).Start();
}
private void Client_Connected(object sender, EventArgs e) {
if(sender is Server) {
Send = new Send(Server.Instance);
} else {
Send = new Send(Client.Instance);
}
}
private void SendBitmap() {
if(Send == null) { return; }
new Thread(() => {
while(Send != null) {
Bitmap b = CaptureBitmap();
if(b != null) {
Send.Bitmap(b);
Thread.Sleep(16); //around 60 times a second
}
}
}).Start();
}
private void Bitmap_Received(object sender, BitmapEventArgs e) {
if(Send != null && e.Bitmap != null) {
BitmapPicBox.Image = e.Bitmap;
}
}
private Bitmap CaptureBitmap() {
IntPtr deskHandle = WinApi.GetDesktopWindow();
IntPtr dc = WinApi.GetWindowDC(deskHandle);
if(dc != IntPtr.Zero) {
if(WinApi.GetWindowRect(deskHandle, out WinApi.RECT winRect)) {
IntPtr hdcDest = WinApi.CreateCompatibleDC(dc);
IntPtr hBitmap = WinApi.CreateCompatibleBitmap(dc, winRect.Width, winRect.Height);
IntPtr hOld = WinApi.SelectObject(hdcDest, hBitmap);
if(WinApi.BitBlt(hdcDest, 0, 0, winRect.Width, winRect.Height, dc, winRect.Left, winRect.Top, WinApi.TernaryRasterOperations.SRCCOPY)) {
WinApi.SelectObject(hdcDest, hOld);
WinApi.DeleteDC(hdcDest);
WinApi.ReleaseDC(deskHandle, dc);
Bitmap b = Image.FromHbitmap(hBitmap);
WinApi.DeleteObject(hBitmap);
return b;
} else {
WinApi.DeleteDC(hdcDest);
WinApi.ReleaseDC(deskHandle, dc);
WinApi.DeleteObject(hBitmap);
}
}
}
return null;
}
}
I have since tried a SharpDX implementation. I used the example from this github project for getting desktop image by desktop duplication and feeding that to a panel on the form which renders it.
But I'm not sure how I'd integrate this into my socket connection, I'd need to pass the frame being created as a byte array, and then back into a Bitmap1 that can be rendered.
Receiving the byte array and converting to Bitmap1 would be here:
private void RenderDuplicatedFrame(byte[] bitmapBytes) {
//Convert bitmapBytes to SharpDX.Direct2D1.Bitmap1
SharpDX.Direct2D1.Bitmap1 bitmap = //???
_backBufferDc.Value.BeginDraw();
_backBufferDc.Value.Clear(new RawColor4(0, 0, 0, 1));
using(bitmap) {
var renderX = (Size.Width - RenderSize.Width) / 2;
var renderY = (Size.Height - RenderSize.Height) / 2;
_backBufferDc.Value.DrawBitmap(bitmap, new RawRectangleF(renderX, renderY, renderX + RenderSize.Width, renderY + RenderSize.Height), 1, BitmapInterpolationMode.Linear);
}
_backBufferDc.Value.EndDraw();
_swapChain.Value.Present(1, 0);
}
Sending the Bitmap1 would be here:
private void AcquireFrame() {
var od = _outputDuplication.Value;
SharpDX.DXGI.Resource frame;
OutputDuplicateFrameInformation frameInfo;
od.AcquireNextFrame(500, out frameInfo, out frame);
using(frame) {
if(frameInfo.LastPresentTime != 0) {
using(var frameSurface = frame.QueryInterface<Surface>()) {
using(var frameDc = new SharpDX.Direct2D1.DeviceContext(_2D1Device.Value, DeviceContextOptions.EnableMultithreadedOptimizations)) {
using(var frameBitmap = new Bitmap1(frameDc, frameSurface)) {
//Convert frameBitmap to byte array here to send over socket
}
}
}
}
od.ReleaseFrame();
}
}
So how do I go about converting to and from a byte array?

C# CSCore. Can't write pcm-wav from microphone correctly

I am trying to record audio stream from a microphone to file (wav format).
By default, the file is recorded in stereo, the IEEE Float codec, but I need to record audio in PCM-format (16 kHz, Mono)
Where should this format be used in this program code?
(variable need_wave_format)
The project uses NuGet CSCore.
Link to this sample project:
https://github.com/LordKmon/CsCoreRecordProblem
Code from winform:
public partial class Form1 : Form
{
private WasapiCapture m_SoundKeeper;
private IWriteable m_Writer;
private IWaveSource m_FinalSource;
public Form1()
{
InitializeComponent();
}
private void btn_StartRecord_Click(object sender, EventArgs event_args)
{
//Find and set Device (microphone)
MMDevice selected_device = null;
using (var deviceEnumerator = new MMDeviceEnumerator())
using (var deviceCollection = deviceEnumerator.EnumAudioEndpoints(DataFlow.Capture, DeviceState.Active))
{
selected_device = deviceCollection[0];
}
// Format that I needed
WaveFormat need_wave_format = new WaveFormat(16000, 16, 1, AudioEncoding.Pcm);
// Start record
m_SoundKeeper = new WasapiCapture();
m_SoundKeeper.Device = selected_device;
m_SoundKeeper.Initialize();
var soundInSource = new SoundInSource(m_SoundKeeper);
var singleBlockNotificationStream = new SingleBlockNotificationStream(soundInSource.ToSampleSource());
m_FinalSource = singleBlockNotificationStream.ToWaveSource();
m_Writer = new WaveWriter("output.wav", m_FinalSource.WaveFormat);
byte[] buffer = new byte[m_FinalSource.WaveFormat.BytesPerSecond / 2];
soundInSource.DataAvailable += (s, e) =>
{
int read;
while ((read = m_FinalSource.Read(buffer, 0, buffer.Length)) > 0)
m_Writer.Write(buffer, 0, read);
};
l_Status.Text = "RECORD !!!";
m_SoundKeeper.Start();
}
private void btn_Stop_Click(object sender, EventArgs e)
{
if (m_SoundKeeper == null)
return;
m_SoundKeeper.Stop();
m_SoundKeeper.Dispose();
m_SoundKeeper = null;
m_FinalSource.Dispose();
if (m_Writer is IDisposable)
((IDisposable)m_Writer).Dispose();
l_Status.Text = "...";
}
}
}
Where do I should to use variable "need_wave_format" in this code so that the output is a file of the Wav-PCM format?

Xamarin Android take screenshot

I am working on a project which makes drawing.
I don't use axml because I do my drawing in a class called filledpolygon and calling the function in MainActivity. I just want to take screenshot in my project. Is there any basic function, which I can call in onCreate method? So, when the program runs, it will automatically take the screenshot. I found answers except Xamarin platform.
Since Android 28 DrawingCacheEnabled is deprecated and without it we are forcing our view to to redraw on our custom canvas wich can cause artifacts with custom controls and renderers and the screenshot version might be different from what we see on screen.
The legacy code that is still working on simple cases is:
public byte[] CaptureScreenshot()
{
var view=
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView;
if (view.Height < 1 || view.Width < 1)
return null;
byte[] buffer = null;
view.DrawingCacheEnabled = true;
using (var screenshot = Bitmap.CreateBitmap(
view.Width,
view.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
view.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
view.DrawingCacheEnabled = false;
return buffer;
}
Use legacy method above as follows
if ((int)Android.OS.Build.VERSION.SdkInt < 28)
{
//legacy
}
The DrawingCacheEnabled obsolete warning redirects us to use PixelCopy. This method is acting with a callback so to use it synchronously have made some helpers:
Usage:
public byte[] CaptureScreenshot()
{
using var helper = new ScreenshotHelper(
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView,
Xamarin.Essentials.Platform.CurrentActivity);
byte[] buffer = null;
bool wait = true;
Task.Run(async () =>
{
helper.Capture((Bitmap bitmap) =>
{
try
{
if (!helper.Error)
{
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
wait = false;
}
});
}).ConfigureAwait(false);
while (wait)
{
Task.Delay(10).Wait();
}
return buffer;
}
The helper:
public class ScreenshotHelper : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
{
public void OnPixelCopyFinished(int copyResult)
{
var stop = true;
if (copyResult == (int) PixelCopyResult.Success)
{
Error = false;
//todo CallbackGotScreenshot();
_callback(_bitmap);
}
else
{
Error = true;
}
_callback(_bitmap);
}
public bool Error { get; protected set; }
public ScreenshotHelper(Android.Views.View view, Activity activity)
{
_view = view;
_activity = activity;
_bitmap = Bitmap.CreateBitmap(
_view.Width,
_view.Height,
Bitmap.Config.Argb8888);
}
// Starts a background thread and its {#link Handler}.
private void StartBackgroundThread()
{
_BackgroundThread = new HandlerThread("ScreeshotMakerBackground");
_BackgroundThread.Start();
_BackgroundHandler = new Handler(_BackgroundThread.Looper);
}
// Stops the background thread and its {#link Handler}.
private void StopBackgroundThread()
{
try
{
_BackgroundThread.QuitSafely();
_BackgroundThread.Join();
_BackgroundThread = null;
_BackgroundHandler = null;
}
catch (Exception e)
{
//e.PrintStackTrace();
}
}
public void Capture(Action<Bitmap> callback)
{
//var locationOfViewInWindow = new int[2];
//_view.GetLocationInWindow(locationOfViewInWindow);
_callback = callback;
try
{
StartBackgroundThread();
//todo could create-use background handler
PixelCopy.Request(_activity.Window, _bitmap, this,
_BackgroundHandler);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Task.Run(StopBackgroundThread);
}
}
private Android.Views.View _view;
private Activity _activity;
private Bitmap _bitmap;
private HandlerThread _BackgroundThread;
private Handler _BackgroundHandler;
private Action<Bitmap> _callback;
public new void Dispose()
{
_bitmap?.Dispose();
_bitmap= null;
_activity = null;
_view = null;
_callback = null;
base.Dispose();
}
}
In your View you could run the following code which will take a screenshot. I have not tried running it in OnCreate() before so you may need to test that out to make sure the view has been fully rendered.
*Edit: According to this post you may have trouble running this code in OnCreate() so you will need to find a better place. I was unable to figure out what post the user was referring to in the link he posted.
*Edit #2: Just found out that Compress() does not take the quality parameter (which is listed as 0 below) into account since PNG is lossless, but if you change the format to JPEG for example, then you may want to turn up the quality parameter since your image will look like garbage.
public byte[] SaveImage() {
DrawingCacheEnabled = true; //Enable cache for the next method below
Bitmap bitmap = GetDrawingCache(true); //Gets the image from the cache
byte[] bitmapData;
using(MemoryStream stream = new MemoryStream()) {
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}

(Pause) Stop download without dropping connection

I want to be able to pause a download. I can stop them by dropping the existing connections.
What I'm referring to is almost similar to what's described here: https://superuser.com/questions/170509/whats-the-difference-in-using-pause-stop-in-%C2%B5torrent
My download class:
public class Download
{
public event EventHandler<DownloadStatusChangedEventArgs> DownloadStatusChanged;
public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
public event EventHandler DownloadCompleted;
public bool stop = true; // by default stop is true
public void DownloadFile(string DownloadLink, string Path)
{
stop = false; // always set this bool to false, everytime this method is called
long ExistingLength = 0;
FileStream saveFileStream;
if (File.Exists(Path))
{
FileInfo fileInfo = new FileInfo(Path);
ExistingLength = fileInfo.Length;
}
if (ExistingLength > 0)
saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
else
saveFileStream = new FileStream(Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
request.Proxy = null;
request.AddRange(ExistingLength);
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
long FileSize = ExistingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
bool downloadResumable; // need it for sending empty progress
if ((int)response.StatusCode == 206)
{
Console.WriteLine("Resumable");
var downloadStatusArgs = new DownloadStatusChangedEventArgs();
downloadResumable = true;
downloadStatusArgs.ResumeSupported = downloadResumable;
OnDownloadStatusChanged(downloadStatusArgs);
}
else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
{
Console.WriteLine("Resume Not Supported");
ExistingLength = 0;
var downloadStatusArgs = new DownloadStatusChangedEventArgs();
downloadResumable = false;
downloadStatusArgs.ResumeSupported = downloadResumable;
OnDownloadStatusChanged(downloadStatusArgs);
// restart downloading the file from the beginning because it isn't resumable
// if this isn't done, the method downloads the file from the beginning and starts writing it after the previously half downloaded file, thus increasing the filesize and corrupting the downloaded file
saveFileStream.Dispose(); // dispose object to free it for the next operation
File.WriteAllText(Path, string.Empty); // clear the contents of the half downloaded file that can't be resumed
saveFileStream = saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); // reopen it for writing
}
using (var stream = response.GetResponseStream())
{
byte[] downBuffer = new byte[4096];
int byteSize = 0;
long totalReceived = byteSize + ExistingLength;
var sw = new Stopwatch();
sw.Start();
while ((byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
saveFileStream.Write(downBuffer, 0, byteSize);
totalReceived += byteSize;
var args = new DownloadProgressChangedEventArgs();
args.BytesReceived = totalReceived;
args.TotalBytesToReceive = FileSize;
float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
args.CurrentSpeed = currentSpeed;
if (downloadResumable == true)
{
args.ProgressPercentage = ((float)totalReceived / (float)FileSize) * 100;
long bytesRemainingtoBeReceived = FileSize - totalReceived;
args.TimeLeft = (long)(bytesRemainingtoBeReceived / currentSpeed);
}
else
{
//args.ProgressPercentage = Unknown;
//args.TimeLeft = Unknown;
}
OnDownloadProgressChanged(args);
if (stop == true)
return;
}
sw.Stop();
}
}
var completedArgs = new EventArgs();
OnDownloadCompleted(completedArgs);
saveFileStream.Dispose();
}
catch (WebException e)
{
string filename = System.IO.Path.GetFileName(Path);
Console.WriteLine(e.Message);
saveFileStream.Dispose();
return; //not needed because this is the last line of the method, but let's keep it here
}
}
public void StopDownload()
{
stop = true;
}
protected virtual void OnDownloadStatusChanged(DownloadStatusChangedEventArgs e)
{
EventHandler<DownloadStatusChangedEventArgs> handler = DownloadStatusChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnDownloadProgressChanged(DownloadProgressChangedEventArgs e)
{
EventHandler<DownloadProgressChangedEventArgs> handler = DownloadProgressChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnDownloadCompleted(EventArgs e)
{
EventHandler handler = DownloadCompleted;
if (handler != null)
{
handler(this, e);
}
}
}
public class DownloadStatusChangedEventArgs : EventArgs
{
public bool ResumeSupported { get; set; }
}
public class DownloadProgressChangedEventArgs : EventArgs
{
public long BytesReceived { get; set; }
public long TotalBytesToReceive { get; set; }
public float ProgressPercentage { get; set; }
public float CurrentSpeed { get; set; } // in bytes
public long TimeLeft { get; set; } // in seconds
}
Can anybody help me with this?
Note: I can already resume downloads, that's not what I want, if you were wondering.
In your code (you should copy it into your question, by the way), you have a loop that reads bytes from a stream. You have a flag that will stop the loop.
You do the exact same thing except for pausing instead of stopping. It could be another flag, but you will also need to know when to resume, so you need something for the loop to wait on. One fairly neat way to do this would be with a lock (mutex).
I don't know C# so I can't give you working code, but here is pseudocode for it. With your pointer to the reference docs, it looks like SemaphoreSlim is the simplest thing suitable for this purpose.
bool stop = false
bool paused = false
SemaphoreSlim pauseLock = new SemaphoreSlim(1)
method download():
while (stuff to read):
read from stream
write to file
if stop:
break
// This will do nothing if not paused, or will block if paused
pauseLock.Wait()
pauseLock.Release()
method stop():
stop = true
self.unpause() // stop waiting on lock if needed
method pause()
if not paused:
paused = true
// Note this cannot block for more than a moment
// since the download thread doesn't keep the lock held
pauseLock.Wait()
method unpause()
if paused:
paused = false
pauseLock.Release()
Of course, doing this may cause the server to drop your connection eventually (at which point you need to resume anyway, and you'll only notice this after unpausing).

Categories

Resources