Blip.TV是一个非常受欢迎的视频共享网站,其提供了一个基于REST的文件上传的API。可以使用它在捕获的应用程序上构建一个视频共享机制,或者甚至将它作为一个独立的应用程序。
Blip.TV上传API的在线文档位于http://wiki.Blip.TV/index.php/REST_Upload_API,它详细介绍了在请求中可能包含的各种元素。特别是需要将上传文件的元素命名为“file”。为了运行示例代码,需要一个常规的Blip.TV用户登录(用户名)和密码(在实际应用程序中,它们应该由用户提供)。而且还需要一个title(标题),同时需要包含值为“1”的post以及值为“api”的skin,以返回XML形式的响应。
一旦通过API将视频上传到Blip.TV,他们就将以类似如下的XML进行响应:
<response>
<current_time>2010-10-30T00:13:00Z</current_time>
<timestamp>1288397581081</timestamp>
<status>OK</status>
<payload>
<asset>
<timestamp>1288397580</timestamp>
<id>4332695</id>
<item_type>file</item_type>
<item_id>4314031</item_id>
<links>
<link rel="alternate" type="text/html" href="http://blip.tv/file/4314031/" />
<link rel="alternate" type="application/rss+xml" href="http://blip.tv/ree/4332695/" />
<link rel="alternate" type="application/atom+xml" href="http://blip.tv/file/4314031/?skin=atom" />
<link rel="service.edit" type="text/html" href="http://blip.tv/file/post/4314031/" />
<link rel="service.edit" type="text/html" href="http://blip.tv/file/post/4314031/?skin=api" />
</links>
<files>
<file src="Username-AVideo562.3gp" submitted_as="VID_20101029_200900.3gp" role='Source'/>
</files>
</asset>
</payload>
</response>
注意,如果上传成功,那么XML给出的状态是OK,并且在file元素中给出指向原始文件的链接。可以使用SAX分析器来分析这个XML,寻找这些条目并将视频返回给用户,以检查上传是否成功。
如果上传失败,那么XML给出的状态时ERROR,并给出一个error的标记,其中包括编码和消息。下面是一个用户名/密码组合错误输入的示例。
<response>
<current_time>2010-10-30T00:18:32Z</current_time>
<timestamp>1288399112662</timestamp>
<status>ERROR</status>
<error>
<code>AUTHENTICATION_REQUIRED</code>
<message>the operation you attempted to perform require authentication information is invalid,missing or insufficient for the action you are attempting to perform.</message>
</error>
</response>
接下来看一下捕获视频并上传到Blip.TV的完整代码:
1 package com.nthm.androidtestActivity; 2 3 import java.io.File; 4 import java.io.FilterOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.io.UnsupportedEncodingException; 9 import javax.xml.parsers.ParserConfigurationException; 10 import javax.xml.parsers.SAXParser; 11 import javax.xml.parsers.SAXParserFactory; 12 import org.apache.http.HttpEntity; 13 import org.apache.http.HttpResponse; 14 import org.apache.http.client.ClientProtocolException; 15 import org.apache.http.client.HttpClient; 16 import org.apache.http.client.methods.HttpPost; 17 import org.apache.http.entity.mime.MultipartEntity; 18 import org.apache.http.entity.mime.content.FileBody; 19 import org.apache.http.entity.mime.content.StringBody; 20 import org.apache.http.impl.client.DefaultHttpClient; 21 import org.xml.sax.Attributes; 22 import org.xml.sax.InputSource; 23 import org.xml.sax.SAXException; 24 import org.xml.sax.XMLReader; 25 import org.xml.sax.helpers.DefaultHandler; 26 import com.nthm.androidtest.R; 27 import android.app.Activity; 28 import android.content.Intent; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.widget.TextView; 34 35 public class BlipTVUploader extends Activity {
该活动将使用一个意图来触发Camera活动以进行视频录制,同时触发默认的活动以进行视频播放,因此需要设置两个常量来获知返回了哪个活动。
1 private final static int VIDEO_CAPTURED=0; 2 private final static int VIDEO_PLAYED=0;
还需要几个变量:File变量表示SD卡上的捕获视频,字符串类型的title是上传到Blip.TV时的视频标题,还有用于Blip.TV的username和password。在实际应用程序中,应该从用户处获得title、username和password。
1 private File videoFile; 2 private String title="A Video"; 3 private String username="BLIPTV_USERNAME"; 4 private String password="BLIPTV_PASSWORD";
postingResult变量将包含上传之后在Blip.TV的XML响应中给定的结果。
1 private String postingResult="";
在录制视频之后,设置fileLength变量,从而可以跟踪文件的上传进度。
1 private long fileLength=0;
我们将使用一个TextView来向用户显示上传的进度和其他信息。
1 private TextView textview; 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.bliptvuploader); 7 textview=(TextView) findViewById(R.id.textview);
当第一次启动时,我们将使用一个意图来触发默认的视频捕获应用程序,通常是启动内置的Camera活动,使得用户能够捕获视频。将VIDEO_CAPTURED常量连同意图一起传递给startActivityForResult,从而在onActivityResult中可以知道返回的是什么内容。
1 Intent captureVideoIntent=new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); 2 startActivityForResult(captureVideoIntent, VIDEO_CAPTURED); 3 } 4 5 @Override 6 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
当Camera活动返回时可以获得视频文件的Uri,就像是以第11章所描述的方式捕获它一样。
1 super.onActivityResult(requestCode, resultCode, data); 2 if(resultCode==RESULT_OK&&requestCode==VIDEO_CAPTURED){ 3 Uri videoFileUri=data.getData();
为了获得SD卡上的实际视频文件,需要查询MediaStore,请求表示该文件的DATA列。
1 String [] columns={android.provider.MediaStore.Video.Media.DATA}; 2 Cursor cursor=managedQuery(videoFileUri, columns, null, null, null); 3 int fileColumn=cursor.getColumnIndexOrThrow(android.provider.MediaStore.Video.Media.DATA); 4 if(cursor.moveToFirst()){ 5 String videoFilePath=cursor.getString(fileColumn);
一旦获得了该文件的路径,就可以构造File对象,获得它的长度,并实例化一个BlipTVFilePoster对象。由于BlipTVFilePoster扩展了AsyncTask,因此为了启动它以使其执行操作,可以调用它的execute方法。
1 videoFile=new File(videoFilePath); 2 fileLength=videoFile.length(); 3 BlipTVFilePoster tvfp=new BlipTVFilePoster(); 4 tvfp.execute(); 5 }
如果返回的是视频播放器活动,那么只须简单的结束,这样就完成了所有的工作。
1 }else if(requestCode==VIDEO_PLAYED){ 2 finish(); 3 } 4 }
如前所述,BlipTVFilePoster扩展了AsyncTask。因此,它可以在后台线程中工作,而不会绑定界面。此外,它还会实现ProgressListener,该接口旨在处理来自上传类的进度回调;同时它还实现了BlipXMLParserListener,从而处理来自XML分析类的回调。
1 class BlipTVFilePoster extends AsyncTask<Void, String, Void> implements ProgressListener,BlipXMLParserListener{
变量videoUrl将包含上传至Blip.TV之后指向视频文件的URL。
1 private String videoUrl; 2 @Override 3 protected Void doInBackground(Void... params) {
如前所述,可以通过MultipartEntity使用HttpClient来执行HTTP文件上传。
1 HttpClient httpclient=new DefaultHttpClient(); 2 HttpPost httppost=new HttpPost("http://blip.tv/file/post");
在此示例中使用了一个ProgressMultipartEntity类,其扩展了MultipartEntity,但是允许跟踪传递的过程。因为实现了ProgressListener,所以可以将自身作为监听器传入。
1 ProgressMultipartEntity multipartEntity =new ProgressMultipartEntity(this);
需要在帖子中添加Blip.TV所需的几个部分。当然,我们需要file,但是还将需要userlogin(用户名)、password、title、值为“1”的post——从而会真正发布它,并且需要值为“api”的skin——从而能够获得XML作为响应,而不是获得一个正常的网页。
1 try { 2 multipartEntity.addPart("file",new FileBody(videoFile)); 3 multipartEntity.addPart("userlogin", new StringBody(username)); 4 multipartEntity.addPart("password", new StringBody(password)); 5 multipartEntity.addPart("title", new StringBody(title)); 6 multipartEntity.addPart("post", new StringBody("1")); 7 multipartEntity.addPart("skin", new StringBody("api")); 8 httppost.setEntity(multipartEntity); 9 HttpResponse httpresponse=httpclient.execute(httppost); 10 HttpEntity responseentity=httpresponse.getEntity(); 11 if(responseentity!=null){
一旦执行完HTTP文件上传,就可以获得一个InputStream,以从服务器读取响应。在当前情况下,只需要将该InputStream传递给SAX分析器的一个实现,从而可以确定上传是否成功。
1 InputStream inputstream=responseentity.getContent(); 2 SAXParserFactory aSAXParserFactory=SAXParserFactory.newInstance(); 3 try { 4 SAXParser aSAXParser=aSAXParserFactory.newSAXParser(); 5 XMLReader anXMLReader=aSAXParser.getXMLReader();
我们将使用下面定义的BlipResponseXMLHandler来专门处理在上传文件之后来自Blip.TV的XML响应。
1 anXMLReader.setContentHandler(xmlHandler); 2 anXMLReader.parse(new InputSource(inputstream)); 3 } catch (ParserConfigurationException e) { 4 e.printStackTrace(); 5 } catch (SAXException e) { 6 e.printStackTrace(); 7 } 8 inputstream.close(); 9 } 10 } catch (UnsupportedEncodingException e) { 11 e.printStackTrace(); 12 }catch (ClientProtocolException e) { 13 e.printStackTrace(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 } 17 return null; 18 }
正如通常的AsyncTask一样,当调用publishProgress时最终将触发onProgressUpdate方法,但是与doInBackground方法不同,该方法可以直接与UI线程交互。
1 @Override 2 protected void onProgressUpdate(String... values) { 3 textview.setText(values[0]); 4 }
当完成doInBackground方法时,触发onPostExecute方法,当发生该事件时,如果XML分析器已经填充了videoUrl变量,就只须创建一个意图,以使用默认的视频播放器活动播放上传的文件。
1 @Override 2 protected void onPostExecute(Void result) { 3 if(videoUrl!=null){ 4 Intent viewVideoIntent=new Intent(Intent.ACTION_VIEW); 5 Uri uri=Uri.parse("http://blip.tv/file/get/"+videoUrl); 6 viewVideoIntent.setDataAndType(uri, "video/3gpp"); 7 startActivityForResult(viewVideoIntent, VIDEO_PLAYED); 8 } 9 }
下面定义的transferred方法是ProgressListener接口必须实现的一部分。当文件正在上传时,该方法将被ProgressMultipartEntity调用。transferred方法将调用publishProgress方法,这将触发onProgressUpdate方法来更新UI。
1 @Override 2 public void transferred(long num) { 3 double percent=(double)num/(double)fileLength; 4 int percentInt=(int)(percent*100); 5 publishProgress(""+percentInt+"% Transferred"); 6 }
parseResult方法是BlipXMLParserListener实现所必须的一部分。当正在分析应向用户报告的XML时将调用这个方法。它仅仅调用publishProgress,这回触发onProgressUpdate以向用户显示结果。
1 @Override 2 public void parseResult(String result) { 3 publishProgress(result); 4 }
setVideoUrl方法也是BlipXMLParserListener实现所必须的一部分。它只是在已上传视频后使用视频的URL填充videoUrl变量。
1 @Override 2 public void setVideoUrl(String url) { 3 videoUrl=url; 4 } 5 }
下面扩展了MultipartEntity的ProgressMultipartEntity。这个类包含了ProgressListener,从而可以报告进度;同时它重写了writeTo方法,以使用可以计算流出字节数的OutputStream。
1 class ProgressMultipartEntity extends MultipartEntity{ 2 ProgressListener progressListener; 3 4 public ProgressMultipartEntity(ProgressListener progressListener) { 5 super(); 6 this.progressListener = progressListener; 7 } 8 9 @Override 10 public void writeTo(OutputStream outstream) throws IOException { 11 super.writeTo(new ProgressOutputStream(outstream, this.progressListener)); 12 } 13 }
ProgressListener接口非常的简单,它仅仅指定在实现类中需要有一个transferred方法。
1 interface ProgressListener{ 2 void transferred(long num); 3 }
下面是ProgressOutputStream,其重写了FilterOutputStream中的write方法,同时跟踪已传输的字节数。
1 static class ProgressOutputStream extends FilterOutputStream{ 2 ProgressListener listener; 3 int transferred; 4 public ProgressOutputStream(OutputStream out,ProgressListener listener) { 5 super(out); 6 this.listener=listener; 7 this.transferred=0; 8 } 9 @Override 10 public void write(byte[] buffer, int offset, int length) 11 throws IOException { 12 out.write(buffer, offset, length); 13 this.transferred+=length; 14 this.listener.transferred(this.transferred); 15 } 16 @Override 17 public void write(int oneByte) throws IOException { 18 out.write(oneByte); 19 this.transferred++; 20 this.listener.transferred(this.transferred); 21 } 22 }
最后还包括BlipXMLParserListener接口和BlipResponseXMLHandler,他们处理Blip.TV为响应文件上传而传输的XML。
1 interface BlipXMLParserListener{ 2 void parseResult(String result); 3 void setVideoUrl(String url); 4 } 5 class BlipResponseXMLHandler extends DefaultHandler{
下面的常量将结合state变量来跟踪当前在XML中的位置。
1 int NONE=0; 2 int ONSTATUS=1; 3 int ONFILE=2; 4 int ONERRORMESSAGE=3; 5 6 int state=NONE;
下一组常量定义XML中可能返回的状态值。我们将只用这些常量来跟踪下面定义的status整数中的状态。
1 int STATUS_UNKNOWN=0; 2 int STATUS_OK=1; 3 int STATUS_ERROR=2; 4 5 int status=STATUS_UNKNOWN;
message变量将包含从XML返回的错误信息;或者如果上传成功,那么它包含视频的URL。
1 String message="";
当然,需要保存通过构造函数传入的BlipXMLParserListener。
1 BlipXMLParserListener listener; 2 3 public BlipResponseXMLHandler(BlipXMLParserListener bxpl) { 4 super(); 5 listener=bxpl; 6 } 7 8 @Override 9 public void startDocument() throws SAXException { 10 super.startDocument(); 11 } 12 13 @Override 14 public void endDocument() throws SAXException { 15 super.endDocument(); 16 }
需要在startElement标记中完成许多的工作。检查包含XML标记名称的localName变量,以判断它是否匹配任何需要注意的内容;如果匹配,那么将state变量设置为合适的常量。
1 @Override 2 public void startElement(String uri, String localName, String qName, 3 Attributes attributes) throws SAXException { 4 if(localName.equalsIgnoreCase("status")){ 5 state=ONSTATUS; 6 }else if(localName.equalsIgnoreCase("file")){ 7 state=ONFILE;
如果是file元素,那么通知监听器,同时提取出src属性,它是上传文件之后Blip.TV分配给他的文件名。
1 listener.parseResult("onFile"); 2 message=attributes.getValue("src"); 3 listener.parseResult("filemessage:"+message);
随后,通过setVideoUrl方法将文件传递给BlipXMLParserListener。
1 listener.setVideoUrl(message); 2 }else if(localName.equalsIgnoreCase("message")){ 3 state=ONERRORMESSAGE; 4 listener.parseResult("onErrorMessage"); 5 } 6 } 7 8 @Override 9 public void endElement(String uri, String localName, String qName) 10 throws SAXException { 11 if(localName.equalsIgnoreCase("status")){ 12 state=NONE; 13 }else if(localName.equalsIgnoreCase("file")){ 14 state=NONE; 15 }else if(localName.equalsIgnoreCase("message")){ 16 state=NONE; 17 } 18 }
当在一个元素中发现任何内容时,将触发characters方法。如果state变量表明正在读取一个需要关注的元素,那么会采取相应的操作。
1 @Override 2 public void characters(char[] ch, int start, int length) 3 throws SAXException { 4 String stringChars=new String(ch, start, length);
如果正在读取状态元素,那么将status变量设置为合适的常量。
1 if(state==ONSTATUS){ 2 if(stringChars.equalsIgnoreCase("OK")){ 3 status=STATUS_OK; 4 }else if(stringChars.equalsIgnoreCase("ERROR")){ 5 status=STATUS_ERROR; 6 }else{ 7 status=STATUS_UNKNOWN; 8 }
如果正在读取错误消息元素,那么获得相应的文本并将它设置为message变量,同时将它发送到监听器。
1 }else if(state==ONERRORMESSAGE){ 2 message+=stringChars.trim(); 3 listener.parseResult(message); 4 } 5 } 6 } 7 }
在上述示例中用来跟踪文件上传进度的方法是基于“tuler”所提供的答案。该答案是由ColinD编辑的关于栈溢出(Stack Overflow)问题的回答,栈溢出问题由SoaperGEM在如下页面提出:
http://stackoverflow.com/questions/254719/file-upload-with-java-with-progressbar/470047#470047。
下面是上述活动使用的布局XML。
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" 5 > 6 <TextView 7 android:id="@+id/textview" 8 android:text="" 9 android:layout_width="fill_parent" 10 android:layout_height="wrap_content" 11 android:textSize="35dip"></TextView> 12 13 </LinearLayout>
上述示例演示了如何允许用户直接将创建的视频发布到一个网络视频共享平台。当然,类似的代码还可以用于发布到其他的共享平台,且不限于视频:可以将图像上传到Flickr或Picasa;也可以将音频文件上传到音频共享网站。