Asynchronous Video Downloader

I've been exploring asynchronous operations in ASP.NET 2.0 and while I don't have anything original to contribute you might find how I used this technique interesting. Even when the code is well documented it can be useful to know how it might be applicable to a specific problem.

Awhile ago I figured out how to authenticate for Seesmic API calls using ASP.NET 2.0 based on some PHP sample code. I continued to work with the Seesmic API and expanded my code to get user videos after successfully authenticating. I noticed that in addition to providing a thumbnail of the user video, Seesmic also gives you the url to a FLV file (i.e. Flash Video). This is probably intended for a video player but I decided that I wanted my code to download the videos.

Since downloading a video file can be time consuming and may cause an ASP.NET page to timeout, I wanted the video download to occur asynchronously. However this required making asynchronous web requests in a loop and all of the sample code I found did not attempt to make more than one asynchronous web service request. Initially I tried the old method of doing asynchronous operations which requires a callback delegate. However my ASP.NET page was timing out and there is a new way to do this in ASP.NET 2.0, see the MSDN article Asynchronous Pages in ASP.NET 2.0. I had better luck with the new method of creating asynchronous pages. I was able to successfully download 20 video files totaling 29 MB without causing the ASP.NET page to timeout. I think that is pretty impressive.

Below is my sample code for this little experiment. You will need a Seesmic account and since it is still an "alpha release" this means you have to request an account. If the code appears cut off remember that it is still there, merely hidden, and will be there when you copy and paste. There is an username hard coded in the GetUserVideos function so you may want to change that.

I'm now working on a new project involving converting a PHP script, which uses the GD Library to create dynamic images, into ASP.NET. So far it looks doable and it should be quite innovative.

ASPX Code

   1: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="SeesmicAuth.aspx.vb" Inherits="SeesmicAuth" title="Seesmic API Authentication" Async="true" AsyncTimeout="3000" %>
   2: <asp:Content ID="Content1" ContentPlaceHolderID="cphFull" Runat="Server">
   3: <div class="Box">
   4:   <div class="BoxTitle">
   5:     <asp:Label ID="Label1" runat="server">Seesmic API Authentication Login</asp:Label>
   6:   </div>
   7:   <div class="BoxContent">
   8:   <!-- page content begins -->
   9:   <asp:PlaceHolder ID="phLoginForm" runat="server"> 
  10:         <h2>Please login using your seesmic account</h2>
  11:         <div style="width:200px;float:left;text-align:right;">
  12:         <p>Enter username: <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></p>
  13:         <p>Enter password: <asp:TextBox ID="txtPassword" runat="server"></asp:TextBox></p>
  14:         <asp:Button ID="btnSubmit" Text="Submit" runat="server" />
  15:         <br clear="all" /><br />no information is stored on this server in this process
  16:   </asp:PlaceHolder>
  17:   <asp:PlaceHolder ID="phSuccess" runat="server" Visible="false"> 
  18:         <h2>You have authenticated successfully!</h2>
  19:         Your current sid is: <asp:Label ID="lblSID" Font-Bold="true" runat="server"></asp:Label> and the session_id is <asp:Label ID="lblSessionID" Font-Bold="true" runat="server"></asp:Label>
  20:   </asp:PlaceHolder>
  21:   <asp:PlaceHolder ID="phFailure" runat="server" Visible="false"> 
  22:         <h2 style="color:red;">Seesmic login failed</h2>
  23:   </asp:PlaceHolder>
  24:   <br /><br />
  25:   <asp:PlaceHolder ID="phVideos" runat="server" Visible="false"> 
  26:   </asp:PlaceHolder>
  27:   <!-- page content ends -->
  28:    </div>
  29: </div>  
  30: </asp:Content>
  31:  
  32:  

VB.NET Code

   1: Imports System.Net
   2: Imports System.IO
   3: Imports System.Text
   4: Imports System.Security.Cryptography
   5: Imports System.Web.Script.Serialization
   6: Imports System.Diagnostics
   7: Imports System.Collections.Generic
   8: Imports System.Xml
   9: Imports System.Threading
  10:  
  11: Partial Class SeesmicAuth
  12:     Inherits System.Web.UI.Page
  13:  
  14:     Private Delegate Sub DownloadFLVDelegate(ByVal urlFlv As String)
  15:  
  16:     Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
  17:         Dim seesmicAuthUrl As String = ""
  18:         seesmicAuthUrl = "http://api.seesmic.com/login/auth?username=" & txtUserName.Text & "&password=" & GenerateMD5Hash(txtUserName.Text, txtPassword.Text)
  19:         Debug.WriteLine(seesmicAuthUrl, "seesmicAuthUrl")
  20:  
  21:         ' Make a web request
  22:         Dim HttpSite As Uri = New Uri(seesmicAuthUrl)
  23:         Dim objHttpWebRequest As HttpWebRequest = CType(WebRequest.Create(HttpSite), HttpWebRequest)
  24:         objHttpWebRequest.KeepAlive = False
  25:         objHttpWebRequest.Timeout = 30000
  26:  
  27:         '  Get the response
  28:         Dim objWebResponse As WebResponse = objHttpWebRequest.GetResponse()
  29:         Dim objStreamReader As StreamReader = New StreamReader(objWebResponse.GetResponseStream(), System.Text.Encoding.UTF8)
  30:  
  31:         Dim strJsonResponse = String.Empty
  32:         Dim js As New JavaScriptSerializer
  33:         Dim objDictionary As Dictionary(Of String, Object)
  34:  
  35:         ' Read the response using a stream reader
  36:         strJsonResponse = objStreamReader.ReadToEnd()
  37:         ' Deserialize the JSON string into a dictionary object
  38:         objDictionary = js.DeserializeObject(strJsonResponse)
  39:         Debug.WriteLine(strJsonResponse, "strJsonResponse")
  40:  
  41:         ' The value of the login key is another dictionary object
  42:         Dim objSubDictionary As Dictionary(Of String, Object) = objDictionary("login")
  43:  
  44:         ' Note: Make sure the dictionary contains a key before trying to reference it!
  45:         If objSubDictionary.ContainsKey("success") Then
  46:             If objSubDictionary("success") = "true" Then
  47:                 ' Authenticated, show sid and session_id values
  48:                 phLoginForm.Visible = False
  49:                 phSuccess.Visible = True
  50:                 If objSubDictionary.ContainsKey("sid") Then
  51:                     lblSID.Text = objSubDictionary("sid")
  52:                 End If
  53:                 If objSubDictionary.ContainsKey("session_id") Then
  54:                     lblSessionID.Text = objSubDictionary("session_id")
  55:                 End If
  56:                 ' Make an API request
  57:                 GetUserVideos(objSubDictionary("sid"))
  58:             Else
  59:                 ' Authentication failed, show a different message
  60:                 phLoginForm.Visible = False
  61:                 phFailure.Visible = True
  62:             End If
  63:         End If
  64:  
  65:         ' Debugging authentication failures
  66:         If objSubDictionary.ContainsKey("success") Then
  67:             Debug.WriteLine(objSubDictionary("success"), "success")
  68:         End If
  69:         If objSubDictionary.ContainsKey("reason") Then
  70:             Debug.WriteLine(objSubDictionary("reason"), "reason")
  71:         End If
  72:  
  73:         ' Code to figure out what objects JavaScriptSerializer.DeserializeObject returns
  74:         'For Each obj As Object In d
  75:         '    Debug.WriteLine(obj.GetType(), "Object Type")
  76:         '    Dim c As KeyValuePair(Of String, Object) = DirectCast(obj, KeyValuePair(Of String, Object))
  77:         '    Debug.WriteLine(c.Key, "Key")
  78:         '    Debug.WriteLine(c.Value, "Value")
  79:         '    Dim f As Dictionary(Of String, Object) = c.Value
  80:         '    Debug.WriteLine(f.Count, "Value")
  81:         '    Debug.WriteLine(f("reason"), "reason")
  82:         'Next
  83:  
  84:         objStreamReader.Close()
  85:         objWebResponse.Close()
  86:     End Sub
  87:  
  88:     Private Function GenerateMD5Hash(ByVal username As String, ByVal password As String) As String
  89:         'Create an instance of the MD5CryptoServiceProvider class
  90:         Dim md5Hasher As New MD5CryptoServiceProvider()
  91:         'The array of bytes that will contain the encrypted value 
  92:         Dim hashedBytes As Byte()
  93:         'The encoder class used to convert plain text to an array of bytes
  94:         Dim encoder As New UTF8Encoding()
  95:         'Call ComputeHash, passing in the plain-text string as an array of bytes
  96:         'The return value is the encrypted value, as an array of bytes
  97:         hashedBytes = md5Hasher.ComputeHash(encoder.GetBytes(username & password))
  98:         Debug.WriteLine(BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(), "BitConverter")
  99:         Return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower()
 100:     End Function
 101:  
 102:     Public Sub GetUserVideos(ByVal strSid As String)
 103:         Dim seesmicAPIUrl As String = ""
 104:         seesmicAPIUrl = "http://api.seesmic.com/users/renetto/videos.xml?sid=" & strSid
 105:         Debug.WriteLine(seesmicAPIUrl, "seesmicAPIUrl")
 106:  
 107:         ' Make a web request
 108:         Dim HttpSite As Uri = New Uri(seesmicAPIUrl)
 109:         Dim objHttpWebRequest As HttpWebRequest = CType(WebRequest.Create(HttpSite), HttpWebRequest)
 110:         objHttpWebRequest.KeepAlive = False
 111:         objHttpWebRequest.Timeout = 900000
 112:  
 113:         '  Get the response
 114:         Dim objWebResponse As WebResponse = objHttpWebRequest.GetResponse()
 115:         Dim objStreamReader As StreamReader = New StreamReader(objWebResponse.GetResponseStream(), System.Text.Encoding.UTF8)
 116:         Dim objXmlTextReader As XmlTextReader = New XmlTextReader(objStreamReader)
 117:         Dim objXmlDocument As XmlDocument = New XmlDocument()
 118:         objXmlDocument.Load(objXmlTextReader)
 119:  
 120:         Dim nsMgr = New XmlNamespaceManager(objXmlDocument.NameTable)
 121:         Dim sbHTML As New System.Text.StringBuilder("")
 122:  
 123:         sbHTML.Append("<ul>")
 124:         sbHTML.Append(Environment.NewLine)
 125:  
 126:         Dim strTitle As String = ""
 127:         Dim strUserName As String = ""
 128:         Dim strUrlPlayer As String = ""
 129:         Dim strUrlThumbnail As String = ""
 130:         Dim strCreatedDate As String = ""
 131:  
 132:         Dim objRecordNodeList = objXmlDocument.SelectNodes("records/record", nsMgr)
 133:         For Each objXmlNode As XmlNode In objRecordNodeList
 134:             sbHTML.Append("<li class=""VideoRecord"">")
 135:             sbHTML.Append(Environment.NewLine)
 136:  
 137:             For Each objXmlElement As XmlElement In objXmlNode.ChildNodes
 138:                 If objXmlElement.Name = "title" Then
 139:                     strTitle = objXmlElement.InnerText
 140:                 End If
 141:                 If objXmlElement.Name = "username" Then
 142:                     strUserName = objXmlElement.InnerText
 143:                 End If
 144:                 If objXmlElement.Name = "url-player" Then
 145:                     strUrlPlayer = objXmlElement.InnerText
 146:                 End If
 147:                 If objXmlElement.Name = "url-thumbnail" Then
 148:                     strUrlThumbnail = objXmlElement.InnerText
 149:                 End If
 150:                 If objXmlElement.Name = "created-at" Then
 151:                     strCreatedDate = objXmlElement.InnerText
 152:                 End If
 153:                 If objXmlElement.Name = "url-flv" Then
 154:                     ' download the flv file using an asynchronous page task
 155:                     Dim urlFlv As Uri = New Uri(objXmlElement.InnerText)
 156:                     ' Pass additional parameter using the state object
 157:                     Dim state As Object
 158:                     state = urlFlv.ToString()
 159:                     Dim bh As New BeginEventHandler(AddressOf BeginAsyncTask)
 160:                     Dim eh As New EndEventHandler(AddressOf EndAsyncTask)
 161:                     Dim th As New EndEventHandler(AddressOf TimeoutAsyncOperation)
 162:                     Dim objTask As New PageAsyncTask(bh, eh, th, state)
 163:                     RegisterAsyncTask(objTask)
 164:                 End If
 165:             Next
 166:  
 167:             sbHTML.Append("<h3 class=""VideoTitle"">" & strTitle & "</h3>")
 168:             sbHTML.Append(Environment.NewLine)
 169:             sbHTML.Append("<p>" & strUserName & "</p>")
 170:             sbHTML.Append(Environment.NewLine)
 171:             sbHTML.Append("<a href=""" & strUrlPlayer & """>")
 172:             sbHTML.Append(Environment.NewLine)
 173:             sbHTML.Append("<img src=""" & strUrlThumbnail & """>")
 174:             sbHTML.Append(Environment.NewLine)
 175:             sbHTML.Append("</a>")
 176:             sbHTML.Append(Environment.NewLine)
 177:             sbHTML.Append("<br>")
 178:             sbHTML.Append(Environment.NewLine)
 179:             sbHTML.Append(Convert.ToDateTime(strCreatedDate).ToShortDateString & " " & Convert.ToDateTime(strCreatedDate).ToShortTimeString)
 180:             sbHTML.Append(Environment.NewLine)
 181:  
 182:             sbHTML.Append("</li>")
 183:             sbHTML.Append(Environment.NewLine)
 184:         Next
 185:  
 186:         sbHTML.Append("</ul>")
 187:         sbHTML.Append(Environment.NewLine)
 188:  
 189:         ' save the xml file for future reference
 190:         objXmlDocument.Save(Server.MapPath("App_Data/" & strUserName & "-videos.xml"))
 191:  
 192:         Dim objGenericControl As HtmlControls.HtmlGenericControl = New HtmlControls.HtmlGenericControl
 193:         objGenericControl.InnerHtml = sbHTML.ToString()
 194:         phVideos.Controls.Add(objGenericControl)
 195:         phVideos.Visible = True
 196:  
 197:         objWebResponse.Close()
 198:         objHttpWebRequest.Abort()
 199:     End Sub
 200:  
 201:     Function BeginAsyncTask(ByVal sender As Object, ByVal e As EventArgs, ByVal cb As AsyncCallback, ByVal state As Object) As IAsyncResult
 202:         Dim urlFlv As String = DirectCast(state, String)
 203:         Dim download As New DownloadFLVDelegate(AddressOf DownloadFLV)
 204:         Return download.BeginInvoke(urlFlv, cb, state)
 205:     End Function
 206:  
 207:     Sub EndAsyncTask(ByVal ar As IAsyncResult)
 208:         Debug.WriteLine("End of Asynchronous Task")
 209:     End Sub
 210:  
 211:     Sub TimeoutAsyncOperation(ByVal ar As IAsyncResult)
 212:         Debug.WriteLine("Asynchronous Task timed out downloading file: " & DirectCast(ar.AsyncState, String))
 213:     End Sub
 214:  
 215:     Sub DownloadFLV(ByVal urlFlv As String)
 216:         Dim objWebClient As WebClient = New WebClient()
 217:         Dim strFlvFileName = urlFlv.ToString.Replace("http://v.seesmic.com/flv/", "")
 218:         Try
 219:             Debug.WriteLine("Downloading..." & strFlvFileName)
 220:             objWebClient.DownloadFile(urlFlv, Server.MapPath("App_Data/" & strFlvFileName))
 221:         Catch ex As WebException
 222:             Debug.WriteLine("Error downloading file.." & strFlvFileName)
 223:             Debug.WriteLine(ex.ToString())
 224:         End Try
 225:     End Sub
 226:  
 227: End Class

1 Comment

Comments have been disabled for this content.