Android Video Uploading Application Create Android Studio
My previous tutorial explains how to download a file past showing a progress bar. In this article I am going to explain how to upload a file to server by showing the progress bar. Using this tutorial you can build an app like Instagram where you can capture paradigm or record a video using camera and then upload to a server. On the server side, I used PHP language to read the file and moved it to a detail location.
The best matter well-nigh this article is, it works well with larger file uploads too without whatever out of retentiveness errors. I accept tested the app by uploading 50MB file flawlessly.
Prerequisite
Equally this article uploads the image/video taken from camera, you lot demand to take knowledge over android camera module. So I recommend you go through my previous tutorial Android Working with Camera which gives you lot an overview of integrating photographic camera in your android apps.
1. Creating Android Project
one. In Eclipse create a new android project by navigating to File ⇒ New ⇒ Android Awarding Projection and fill out all the required details.
ii. Open strings.xml located under res ⇒ values and add together below string values.
<?xml version="1.0" encoding="utf-eight"?> <resources> <string name="app_name">Photographic camera File Upload</cord> <string name="btnTakePicture">Capture Image</cord> <cord name="btnRecordVideo">Tape Video</cord> <string name="or">(or)</string> <string name="btnUploadToServer">Upload to Server</cord> </resources>
3. Add below color values in colors.xml located under res ⇒ values folder.
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="view_background">#e8ecfa</color> <color proper name="btn_bg">#277bec</color> <color name="white">#ffffff</color> <color name="txt_font">#4e5572</colour> <colour proper noun="action_bar">#1f2649</colour> </resource>
4. Now nether src folder create a new course named Config.java. This class file contains file upload URL and image directory proper noun to salvage the image/video on mobile retentiveness. Yous will have to supervene upon the file upload url with yours while testing.
package info.androidhive.camerafileupload; public form Config { // File upload url (replace the ip with your server accost) public static concluding String FILE_UPLOAD_URL = "http://192.168.0.104/AndroidFileUpload/fileUpload.php"; // Directory name to store captured images and videos public static terminal String IMAGE_DIRECTORY_NAME = "Android File Upload"; } 5. Create a class named AndroidMultiPartEntity.java and paste below code. This class is a custom MultipartEntity form which provides very of import functionality required for this projection such as progress bar incrementation.
package info.androidhive.camerafileupload; import coffee.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; @SuppressWarnings("deprecation") public class AndroidMultiPartEntity extends MultipartEntity { private final ProgressListener listener; public AndroidMultiPartEntity(last ProgressListener listener) { super(); this.listener = listener; } public AndroidMultiPartEntity(final HttpMultipartMode manner, final ProgressListener listener) { super(mode); this.listener = listener; } public AndroidMultiPartEntity(HttpMultipartMode mode, final Cord purlieus, final Charset charset, final ProgressListener listener) { super(style, purlieus, charset); this.listener = listener; } @Override public void writeTo(last OutputStream outstream) throws IOException { super.writeTo(new CountingOutputStream(outstream, this.listener)); } public static interface ProgressListener { void transferred(long num); } public static grade CountingOutputStream extends FilterOutputStream { individual final ProgressListener listener; private long transferred; public CountingOutputStream(last OutputStream out, final ProgressListener listener) { super(out); this.listener = listener; this.transferred = 0; } public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); this.transferred += len; this.listener.transferred(this.transferred); } public void write(int b) throws IOException { out.write(b); this.transferred++; this.listener.transferred(this.transferred); } } } Now we'll add photographic camera back up in our app by creating a simple screen with two buttons to invoke photographic camera app to capture image or record video.
6. Open your AndroidManifest.xml file and add required permissions. Y'all can observe that UploadActivity also added in below manifest file. Nosotros'll create it in few minutes.
INTERNET – Required to make network calls
WRITE_EXTERNAL_STORAGE – Required to store image/video on to storage
RECORD_AUDIO – Required to tape sound along with video
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" packet="info.androidhive.camerafileupload" android:versionCode="one" android:versionName="1.0" > <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="21" /> <uses-permission android:proper noun="android.permission.Cyberspace" /> <uses-permission android:proper noun="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="info.androidhive.camerafileupload.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" > <intent-filter> <activeness android:name="android.intent.activity.Primary" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:proper name="info.androidhive.camerafileupload.UploadActivity" android:screenOrientation="portrait" > </activity> </awarding> </manifest>
7. Open the layout file of your main activity (activity_main.xml) and add below lawmaking. This creates a layout with two buttons.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/view_background" android:baselineAligned="imitation" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="centre" android:orientation="vertical" > <!-- Capture pic push button --> <Push android:id="@+id/btnCapturePicture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:background="@color/btn_bg" android:paddingLeft="20dp" android:paddingRight="20dp" android:text="@string/btnTakePicture" android:textColor="@color/white" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:gravity="center_horizontal" android:text="@string/or" android:textColor="@colour/txt_font" /> <!-- Record video button --> <Button android:id="@+id/btnRecordVideo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/btn_bg" android:paddingLeft="20dp" android:paddingRight="20dp" android:text="@string/btnRecordVideo" android:textColor="@color/white" /> </LinearLayout> </RelativeLayout>
8. Add beneath camera related code in your MainActivity.coffee class. This lawmaking is directly taken from this tutorial.
In brief what this action will exercise is,
> Camera app will be launched on tapping take picture show or record video button.
> Once the epitome / video is captured, information technology will be stored on to mobile SDCard.
> Finally UploadActivity will be launched by passing the SDCard path of the media that is captured. The process of uploading will exist done in UploadActivity.
package info.androidhive.camerafileupload; import java.io.File; import coffee.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import android.app.Action; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.bone.Bundle; import android.os.Surround; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.widget.Push button; import android.widget.Toast; public class MainActivity extends Activity { // LogCat tag private static terminal String TAG = MainActivity.class.getSimpleName(); // Camera activeness request codes private static terminal int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100; private static final int CAMERA_CAPTURE_VIDEO_REQUEST_CODE = 200; public static last int MEDIA_TYPE_IMAGE = 1; public static last int MEDIA_TYPE_VIDEO = ii; private Uri fileUri; // file url to store paradigm/video private Button btnCapturePicture, btnRecordVideo; @Override protected void onCreate(Package savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Irresolute action bar background colour // These two lines are not needed getActionBar().setBackgroundDrawable(new ColorDrawable(Colour.parseColor(getResources().getString(R.color.action_bar)))); btnCapturePicture = (Push button) findViewById(R.id.btnCapturePicture); btnRecordVideo = (Button) findViewById(R.id.btnRecordVideo); /** * Capture image button click event */ btnCapturePicture.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // capture picture captureImage(); } }); /** * Tape video button click issue */ btnRecordVideo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View five) { // record video recordVideo(); } }); // Checking camera availability if (!isDeviceSupportCamera()) { Toast.makeText(getApplicationContext(), "Sorry! Your device doesn't back up camera", Toast.LENGTH_LONG).prove(); // volition close the app if the device does't accept camera stop(); } } /** * Checking device has photographic camera hardware or not * */ private boolean isDeviceSupportCamera() { if (getApplicationContext().getPackageManager().hasSystemFeature( PackageManager.FEATURE_CAMERA)) { // this device has a camera return true; } else { // no camera on this device return faux; } } /** * Launching photographic camera app to capture image */ private void captureImage() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // start the image capture Intent startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE); } /** * Launching camera app to record video */ private void recordVideo() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO); // set video quality intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, i); intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file // name // start the video capture Intent startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE); } /** * Hither we shop the file url as it will exist null after returning from camera * app */ @Override protected void onSaveInstanceState(Packet outState) { super.onSaveInstanceState(outState); // save file url in packet equally it volition be nothing on screen orientation // changes outState.putParcelable("file_uri", fileUri); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // get the file url fileUri = savedInstanceState.getParcelable("file_uri"); } /** * Receiving activity issue method will be called after endmost the camera * */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent information) { // if the effect is capturing Image if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) { if (resultCode == RESULT_OK) { // successfully captured the image // launching upload activity launchUploadActivity(truthful); } else if (resultCode == RESULT_CANCELED) { // user cancelled Paradigm capture Toast.makeText(getApplicationContext(), "User cancelled image capture", Toast.LENGTH_SHORT) .show(); } else { // failed to capture image Toast.makeText(getApplicationContext(), "Sorry! Failed to capture paradigm", Toast.LENGTH_SHORT) .show(); } } else if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) { if (resultCode == RESULT_OK) { // video successfully recorded // launching upload activity launchUploadActivity(false); } else if (resultCode == RESULT_CANCELED) { // user cancelled recording Toast.makeText(getApplicationContext(), "User cancelled video recording", Toast.LENGTH_SHORT) .prove(); } else { // failed to record video Toast.makeText(getApplicationContext(), "Sorry! Failed to record video", Toast.LENGTH_SHORT) .show(); } } } private void launchUploadActivity(boolean isImage){ Intent i = new Intent(MainActivity.this, UploadActivity.class); i.putExtra("filePath", fileUri.getPath()); i.putExtra("isImage", isImage); startActivity(i); } /** * ------------ Helper Methods ---------------------- * */ /** * Creating file uri to store image/video */ public Uri getOutputMediaFileUri(int type) { return Uri.fromFile(getOutputMediaFile(type)); } /** * returning image / video */ private static File getOutputMediaFile(int blazon) { // External sdcard location File mediaStorageDir = new File( Surround .getExternalStoragePublicDirectory(Surroundings.DIRECTORY_PICTURES), Config.IMAGE_DIRECTORY_NAME); // Create the storage directory if it does non exist if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { Log.d(TAG, "Oops! Failed create " + Config.IMAGE_DIRECTORY_NAME + " directory"); return null; } } // Create a media file proper name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } else if (type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); } else { render null; } return mediaFile; } } Now if you run the app, you should encounter following output.
In one case you are able to launch photographic camera and capture images, we tin can movement frontward and offset creating the upload activity.
9. Create an xml file under res ⇒ layout binder named activity_upload.xml. This layout contains ImageView, VideoView to preview the captured media and a ProgressBar to show uploading progress.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@colour/view_background" android:orientation="vertical" android:padding="10dp" > <!-- To brandish picture taken --> <ImageView android:id="@+id/imgPreview" android:layout_width="fill_parent" android:layout_height="200dp" android:visibility="gone" android:layout_marginTop="15dp"/> <!-- Videoview to preview recorded video --> <VideoView android:id="@+id/videoPreview" android:layout_width="fill_parent" android:layout_height="400dp" android:visibility="gone" android:layout_marginTop="15dp"/> <TextView android:id="@+id/txtPercentage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="15dp" android:layout_marginTop="15dp" android:textColor="@color/txt_font" android:textSize="30dp" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="20dp" android:layout_marginBottom="35dp" android:visibility="gone"/> <Button android:id="@+id/btnUpload" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:groundwork="@color/btn_bg" android:paddingLeft="20dp" android:paddingRight="20dp" android:text="@string/btnUploadToServer" android:textColor="@colour/white" android:layout_marginBottom="20dp"/> </LinearLayout>
10. Create a course named UploadActivity.java and paste beneath code. In this activity
> The path of captured camera epitome/video is received from MainActivity and epitome/video is displayed on the screen for preview purpose.
> UploadFileToServer async method takes care of uploading file to server and updating the Progress Bar.
package info.androidhive.camerafileupload; import info.androidhive.camerafileupload.AndroidMultiPartEntity.ProgressListener; import coffee.io.File; import coffee.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import android.app.Action; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Colour; import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; import android.os.Package; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; public class UploadActivity extends Activity { // LogCat tag private static concluding String TAG = MainActivity.course.getSimpleName(); individual ProgressBar progressBar; private String filePath = zip; private TextView txtPercentage; private ImageView imgPreview; private VideoView vidPreview; private Push button btnUpload; long totalSize = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_upload); txtPercentage = (TextView) findViewById(R.id.txtPercentage); btnUpload = (Push button) findViewById(R.id.btnUpload); progressBar = (ProgressBar) findViewById(R.id.progressBar); imgPreview = (ImageView) findViewById(R.id.imgPreview); vidPreview = (VideoView) findViewById(R.id.videoPreview); // Irresolute action bar background color getActionBar().setBackgroundDrawable( new ColorDrawable(Color.parseColor(getResources().getString( R.color.action_bar)))); // Receiving the information from previous activeness Intent i = getIntent(); // paradigm or video path that is captured in previous activity filePath = i.getStringExtra("filePath"); // boolean flag to place the media type, image or video boolean isImage = i.getBooleanExtra("isImage", true); if (filePath != null) { // Displaying the prototype or video on the screen previewMedia(isImage); } else { Toast.makeText(getApplicationContext(), "Deplorable, file path is missing!", Toast.LENGTH_LONG).show(); } btnUpload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // uploading the file to server new UploadFileToServer().execute(); } }); } /** * Displaying captured paradigm/video on the screen * */ private void previewMedia(boolean isImage) { // Checking whether captured media is image or video if (isImage) { imgPreview.setVisibility(View.VISIBLE); vidPreview.setVisibility(View.GONE); // bimatp factory BitmapFactory.Options options = new BitmapFactory.Options(); // downward sizing image as information technology throws OutOfMemory Exception for larger // images options.inSampleSize = eight; last Bitmap bitmap = BitmapFactory.decodeFile(filePath, options); imgPreview.setImageBitmap(bitmap); } else { imgPreview.setVisibility(View.GONE); vidPreview.setVisibility(View.VISIBLE); vidPreview.setVideoPath(filePath); // starting time playing vidPreview.first(); } } /** * Uploading the file to server * */ private grade UploadFileToServer extends AsyncTask<Void, Integer, String> { @Override protected void onPreExecute() { // setting progress bar to zero progressBar.setProgress(0); super.onPreExecute(); } @Override protected void onProgressUpdate(Integer... progress) { // Making progress bar visible progressBar.setVisibility(View.VISIBLE); // updating progress bar value progressBar.setProgress(progress[0]); // updating percentage value txtPercentage.setText(String.valueOf(progress[0]) + "%"); } @Override protected Cord doInBackground(Void... params) { render uploadFile(); } @SuppressWarnings("deprecation") private String uploadFile() { String responseString = null; HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost(Config.FILE_UPLOAD_URL); try { AndroidMultiPartEntity entity = new AndroidMultiPartEntity( new ProgressListener() { @Override public void transferred(long num) { publishProgress((int) ((num / (float) totalSize) * 100)); } }); File sourceFile = new File(filePath); // Adding file information to http trunk entity.addPart("image", new FileBody(sourceFile)); // Extra parameters if you want to pass to server entity.addPart("website", new StringBody("www.androidhive.info")); entity.addPart("email", new StringBody("abc@gmail.com")); totalSize = entity.getContentLength(); httppost.setEntity(entity); // Making server call HttpResponse response = httpclient.execute(httppost); HttpEntity r_entity = response.getEntity(); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // Server response responseString = EntityUtils.toString(r_entity); } else { responseString = "Error occurred! Http Status Code: " + statusCode; } } catch (ClientProtocolException e) { responseString = e.toString(); } grab (IOException eastward) { responseString = e.toString(); } return responseString; } @Override protected void onPostExecute(Cord event) { Log.e(TAG, "Response from server: " + result); // showing the server response in an alert dialog showAlert(result); super.onPostExecute(result); } } /** * Method to show alert dialog * */ private void showAlert(String bulletin) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(message).setTitle("Response from Servers") .setCancelable(simulated) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // exercise zero } }); AlertDialog alert = builder.create(); alarm.show(); } } Until now nosotros are done with android projection. At present let'south quickly create the PHP project to receive the file that is existence sent from android app. Just before that, we need to practice small configuration changes to WAMP server.
ii. Installing & Configuring WAMP Server
i. Download and install WAMP software. On windows car, WAMP will be installed at C:\wamp location.
2. Open up php.ini and alter below values. By default wamp server allows maximum of 2MB file but to upload. Subsequently changing the beneath values, you tin can upload the files upto 50MB size.
upload_max_filesize = 50M post_max_size = 50M max_input_time = 300 max_execution_time = 300
iii. At present restart the WAMP server.
three. Creating PHP Project
1. Go inside C:\wamp\www and create a folder named AndroidFileUpload. This volition exist the root directory of our project.
2. Now go into AndroidFileUpload folder and create a folder named uploads to keep all the uploaded files.
three. Create a file named fileUpload.php and paste below content. Beneath php lawmaking takes intendance of receiving the files from android app and store them in uploads folder. Upon the processing the file, server responds with a JSON message.
<?php // Path to move uploaded files $target_path = "uploads/"; // assortment for final json respone $response = array(); // getting server ip address $server_ip = gethostbyname(gethostname()); // concluding file url that is being uploaded $file_upload_url = 'http://' . $server_ip . '/' . 'AndroidFileUpload' . '/' . $target_path; if (isset($_FILES['prototype']['name'])) { $target_path = $target_path . basename($_FILES['paradigm']['name']); // reading other post parameters $email = isset($_POST['electronic mail']) ? $_POST['email'] : ''; $website = isset($_POST['website']) ? $_POST['website'] : ''; $response['file_name'] = basename($_FILES['image']['proper noun']); $response['email'] = $e-mail; $response['website'] = $website; endeavor { // Throws exception incase file is non being moved if (!move_uploaded_file($_FILES['paradigm']['tmp_name'], $target_path)) { // brand error flag truthful $response['error'] = true; $response['bulletin'] = 'Could non move the file!'; } // File successfully uploaded $response['message'] = 'File uploaded successfully!'; $response['error'] = faux; $response['file_path'] = $file_upload_url . basename($_FILES['image']['name']); } take hold of (Exception $e) { // Exception occurred. Make mistake flag truthful $response['error'] = true; $response['message'] = $eastward->getMessage(); } } else { // File parameter is missing $response['error'] = true; $response['bulletin'] = 'Not received any file!F'; } // Echo final json response to customer echo json_encode($response); ?> Below is the sample JSON response if the file is uploaded successfully. You lot tin use error value to verify the upload on android side.
{ "file_name": "DSC_0021.JPG", "e-mail": "admin@androidhive.info", "website": "world wide web.androidhive.info", "message": "File uploaded successfully!", "error": false, "file_path": "http://192.168.0.104/AndroidFileUpload/uploads/DSC_0021.JPG" } 4. Testing the File Upload (localhost)
The following steps shows you how to test the both apps together locally.
ane. Connect the both the devices (machine running the wamp server & android mobile) to aforementioned wifi network.
2. Outset the WAMP server.
3. Become the ip address of the machine that is running the PHP project. You lot tin can get the ip accost by typing ipconfig in command prompt. (On mac os, use ifconfig to get the ip accost)
4. Replace the ip address in Config.coffee (check 4th step in android project) with your ip address.
5. Deploy & run the android app on the mobile.
References
1. Stackoverflow Question about file upload with progress bar.
2. Icon that I used every bit app icon.
Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Carmine on Rails and lot more. If you have any thought that you would want me to develop? Let's talk: ravi@androidhive.info
Source: https://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/
0 Response to "Android Video Uploading Application Create Android Studio"
Post a Comment