facebook-csharp-sdk / facebook-csharp-sdk

Facebook SDK for .NET
https://github.com/facebook-csharp-sdk/facebook-csharp-sdk
Apache License 2.0
871 stars 446 forks source link

Error uploading video: "System.IO.IOException: Cannot close stream until all bytes are written." #157

Closed HeidiCJ closed 12 years ago

HeidiCJ commented 12 years ago

Hi,

I’m writing a windows app in Visual Studio 2010 Premium using the Facebook C# SDK ver 6.0.10.0. My OS is Windows 7 Ultimate. I have used the sample code for uploading a video and the token I’m using has publish_stream permission.

My code (which I’ve included below) works fine for very short videos; however, it fails for larger ones. When it fails, it takes it’s time on the following line of code:

dynamic result = fb.Post("/me/videos", parameters);

…and finally it generates the following exception:

System.Net.WebException: The request was aborted: The request was canceled. ---> System.IO.IOException: Cannot close   stream until all bytes are written.

It’s not a Facebook exception, it’s raised by the call to the SDK Post method. Is there some kind of configurable limit in the C# SDK that restricts the size of the file being uploaded? The file I’m uploading is a “wmv” file, 25MB and runs for 30 seconds. I have checked the Facebook documentation here (http://developers.facebook.com/docs/reference/api/video/) and there should be no problems uploading this file.

Can you please help?

Regards, Heidi

private void PostAVideo()
{            
  var fb = new FacebookClient("<TOKEN>");
  byte[] bytes = System.IO.File.ReadAllBytes(@"C:\Videos\Wildlife.wmv");
  dynamic parameters = new ExpandoObject();

  //Create and configure the facebook uploader object
  FacebookMediaObject facebookUploader = new FacebookMediaObject
  {
    ContentType = "video/wmv",
    FileName = "Wildlife.wmv",
  };
  facebookUploader.SetValue(bytes);

  //Set the parameters:
  parameters.source = facebookUploader;
  parameters.title = "Wildlife Video";
  parameters.description = "What a lovely video";
  parameters.method = "video.upload";

  //Post the video - This will be posted into the apps sub folder 
  dynamic result = fb.Post("/me/videos", parameters);
}
prabirshrestha commented 12 years ago

FacebookMediaObject is recommended only for small files because it loads the entire file as byte[].

Can you try using FacebookMediaStream which was introduced in v6. You can learn how to use it at http://csharpsdk.org/docs/making-synchronous-requests.html I would also recommend to use the PostAsync or PostTaskAsync rather then Post.

HeidiCJ commented 12 years ago

Hi and many thanks for your prompt response.

I have modified my code to use the FacebookMediaStream as suggested. PostAsync doesn't appear to exist and I can't use the PostTaskAsync method as it appears to require the "await" command and this was only intrduced in .Net 4.5. I'm using Visual Studio 2010 Premium with .Net 4.0. (please see my modified code at the end of this post)

My code is still failing in the same place:

 dynamic result = fb.Post("/me/videos", parameters);

...and is generating the same error:

  "System.Net.WebException: The request was aborted: The request was canceled. ---> System.IO.IOException: Cannot close stream until all bytes are written."

Do you have any other suggestions?

Thanks, Heidi

public void PostAVideo(string Token)
{
  FacebookClient fb = new FacebookClient(Token);
  string VideoFile = @"C:\Videos\Wildlife.wmv";
  dynamic parameters = new ExpandoObject();

  FacebookMediaStream fbms = new FacebookMediaStream
  {
    ContentType = "video/wmv",
    FileName = System.IO.Path.GetFileName(VideoFile)
  };
  fbms.SetValue(System.IO.File.OpenRead(VideoFile));

  //Set the parameters:
  parameters.source = fbms;
  parameters.title = "Wildlife Video";
  parameters.description = "What a lovely video";
  parameters.method = "video.upload";

  //Post the video - This will be posted into the apps sub folder 
  dynamic result = fb.Post("/me/videos", parameters);
}
prabirshrestha commented 12 years ago

PostAsync is available but hidden from the intellisense.

You don't need .net 4.5 to use TPL. It returns Task<object>, so use ContinueWith.

fb.PostTaskAsync("me/videos", parameters)
   .ContinueWith(t=> Console.WriteLine(t.Result.ToString());

Here is a full sample code on using TPL with Facebook C# SDK. http://blog.prabir.me/post/Facebook-CSharp-SDK-and-Silverlight-5.aspx

prabirshrestha commented 12 years ago

and also please format your code before posting.

press m to see the markdown cheetsheet.

HeidiCJ commented 12 years ago

Hi again,

I implemented the PostTaskAsync call using ContinueWith to call a method that runs when the task has finished (see code sample at the end of this post) and it works a treat. Takes a while to upload (took 30 mins for a 25MB video) but it does work. Many thanks for your help.

I have just one more question. When a task is completed, I need to update my database to say if it was successful or if it there was an error, so when the ContinueWith method is called, I need to be able to identify the post that called it.

Is the a parameter I can set on the dynamic parameters object that gets passed to the PostTaskAsync() call that will then get returned when the task has finished?

Hope this makes sense.

Regards, Heidi

public void PostAVideo(string Token)
{
  FacebookClient fb = new FacebookClient(Token);
  string VideoFile = @"C:\Videos\Wildlife.wmv";
  dynamic parameters = new ExpandoObject();

  //Create and configure the media stream object
  FacebookMediaStream fbms = new FacebookMediaStream
  {
    FileName = Path.GetFileName(VideoFile),
    ContentType = String.Format("video/{0}",Path.GetExtension(VideoFile)) 
  };
  fbms.SetValue(File.OpenRead(VideoFile));

  //Set the parameters:
  parameters.source = fbms;
  parameters.title = String.Format("{0} My Video", DateTime.Now.ToString());
  parameters.description = String.Format("{0} What a lovely video", DateTime.Now.ToString()); 
  parameters.method = "video.upload";

  //make an async post and manage the task it returns
  Action<object> action = RunCompletedAction;
  var task = fb.PostTaskAsync("/me/videos", parameters);
  task.ContinueWith(action); //This will cause the RunCompletedAction mthod to run
}

private void RunCompletedAction(object data)
{
  dynamic d = data;
  if (d.Result != null)
    Console.WriteLine(d.Result.ToString());
} 
prabirshrestha commented 12 years ago

XTaskAsync methods returns Task<object>

so you can continue to use the normal TPL stuffs, I would recommend you to read the TPL docs.

task.ContinueWith(t => {
    if(t.IsCancelled) {
        // cancelled

    } else if (t.IsFaulted) {
        // exception occurred
       t.Wait(); // propagate the exception to the caller.
    } else {
        dynamic result = t.Result;
        var id = result.id;
    }
});
HeidiCJ commented 12 years ago

OK, thanks for the advice. I'm going to go and have a propper read of the TPL and Lambda docs. I have tried your previous TPL samples but every time I do I get this error:

Error   120 Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type.

Always on this part of the code (I get a red squiggle under the t):

t =>

Any idea why?

HeidiCJ commented 12 years ago

Hi,

Do you have a working example of the PostTaskAsync.ContinueWith using a lambda expression ? (as per your last post)

I've tried all the examples you've provided and I just can't get it working. My last attempt uses a delegate but I'm still having no joy. I've searched the internet and can't find any working examples. It's really holding me up now.

Thanks in advance, Heidi

prabirshrestha commented 12 years ago
var fb = new FacebookClient("access_token");

var file = new FacebookMediaStream
               {
                   ContentType = "video/x-ms-wmv",
                   FileName = "Wildlife.wmv"
               }.SetValue(new FileStream(@"C:\Users\Public\Videos\Sample Videos\Wildlife.wmv", FileMode.Open, FileAccess.Read));

fb
  .PostTaskAsync("me/videos", new { file, title = "video upload", description = "desc" })
  .ContinueWith(t =>
                {
                    // make sure to manually dispose the stream correctly.
                    file.Dispose();

                    if (t.IsCanceled)
                    {
                        // handle cancelled
                    }
                    else if (t.IsFaulted)
                    {
                        t.Wait(); // propagate exceptions to the caller
                    }
                    else
                    {
                        dynamic result = t.Result;
                        var id = result.id;
                        // make sure to update in the right thread
                        Console.WriteLine(id.ToString());
                    }
                });
prabirshrestha commented 12 years ago

does it work before I close this issue?

HeidiCJ commented 12 years ago

Hi,

I don't work weekends so I haven't tried it yet. I'll check this morning and let you know.

Regards, Heidi

HeidiCJ commented 12 years ago

Hi,

Thanks so much for your help Prabir, it works a treat!

Regards, Heidi

HeidiCJ commented 12 years ago

Just wanted to say I think I know what the problem was.

This line works perfectly...

.PostTaskAsync("me/videos", new { file, title = "video upload", description = "desc" })

This line gives me an error...

.PostTaskAsync("me/videos", parameters)

The above line gives me the lambda error (below) and a red squiggly underline under "t =>"

Error   120 Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type.

The lambda expression in the ContinueWith call clearly doesn't like the use of a dynamic parameter.

Regards, Heidi

prabirshrestha commented 12 years ago

That is one of the limitations of c#, dynamic does not work with lambdas. If you parameter is dynamic cast it to object.

PostTaskAsync("me/videos", (object)parameters);