Envíe archivos grandes (de vídeo) desde Android a PHP Server
Estoy tratando de enviar un archivo de vídeo de mi dispositivo Android a un servidor (que utiliza PHP para procesar la solicitud). He utilizado volley para otras solicitudes que he hecho a ese mismo servidor, pero cuando intento enviar un archivo de vídeo (como una cadena Base64) al servidor, obtengo un OutOfMemoryError. Me preguntaba si había una manera de enviar el archivo encima de una manera diferente (quizá usando una corriente).
Este es mi método actual para enviar el archivo de vídeo:
- Posicionar el video dentro de un VideoView
- Cómo convertir vídeo para Android usando FFMPEG
- ¿Puedo mostrar el canal de TV actual en una aplicación de pantalla completa de googleTV?
- Android Dev - 3rd Party Media Player SDK u otras opciones?
- ¿Cómo concat o fusionar dos o más archivos de video en Android?
private void syncFullVideos() { checkPermissions(); if (pDialog == null || !pDialog.isShowing()) { pDialog = new ProgressDialog(this); pDialog.setCanceledOnTouchOutside(false); pDialog.setMessage(getString(R.string.pDialogSync)); pDialog.show(); } syncSucces = new boolean[syncVideos.size()]; Arrays.fill(syncSucces, Boolean.FALSE); for (final Video video: syncVideos) { String picturepath = video.getImage_path(); if (fileExists(picturepath)) { File videoFile = new File(picturepath); FileInputStream fin = null; byte[] byte_arr = null; try { fin = new FileInputStream(videoFile); byte_arr = new byte[(int)videoFile.length()]; fin.read(byte_arr); } catch (IOException e) { e.printStackTrace(); } final String image_str = Base64.encodeToString(byte_arr, 0); Log.d("SyncFullVideos", video.toString()); String tag_string_req = "string_req"; final String TAG = AppController.class .getSimpleName(); String url = "http://android.diggin.io/diggin/v1/videos"; StringRequest strReq = new StringRequest(Request.Method.PUT, url, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d(TAG, response); try { final JSONObject jsonObject = new JSONObject(response); if (!jsonObject.getBoolean("error4")) { int index = jsonObject.getInt("index"); syncSucces[index] = true; Log.e("AddSucces(" + (addCount + 1) + "/" + (syncVideos.size()) + ")", video.toString()); addCount++; if (addCount == syncVideos.size()) { refreshPhotos(); } } else { Log.e("AddFail(" + (addCount + 1) + "/" + (syncVideos.size()) + ")", video.toString()); addCount++; if (addCount == syncVideos.size()) { refreshPhotos(); } } } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { VolleyLog.d(TAG, "Error: " + error.getMessage()); //Send message when something goes wrong runOnUiThread(new Runnable() { @Override public void run() { pDialog.hide(); AlertDialog.Builder dlgAlert = new AlertDialog.Builder(PictureActivity.this); dlgAlert.setMessage(R.string.errFullSyncVids); dlgAlert.setPositiveButton(R.string.errBtnOK, null); dlgAlert.setCancelable(true); dlgAlert.create().show(); } }); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<>(); params.put("id", String.valueOf(video.getId())); params.put("im_path", video.getDBImage_path()); params.put("image", image_str); params.put("index", String.valueOf(syncVideos.indexOf(video))); return params; } }; // Adding request to request queue AppController.getInstance().addToRequestQueue(strReq, tag_string_req); } } }
Y este es el PHP que uso para procesar la solicitud y enviar una respuesta (estoy usando la API REST):
$app->put('/videos', function() use ($app) { // check for required params verifyRequiredParams(array('id','im_path','image','index')); $response = array(); $id = $app->request->put('id'); $im_path = $app->request->put('im_path'); $image = $app->request->put('image'); $index = $app->request->put('index'); $db = new DbHandler(); // update photo $result = $db->updateVideo($id, $im_path); if ($result) { $response["error"] = false; $response["message"] = "Video updated successfully"; // Decode Image $binary=base64_decode($image); header('Content-Type: video/mp4'); $im_path2 = explode("_",$im_path); $im_path2[0] .= 's'; $im_path2[2] = $im_path; $im_path3 = implode("/",$im_path2); $filepath = '../images/'.$im_path3; $dirname = dirname($filepath); if (!is_dir($dirname)) { if(mkdir($dirname, 0777, true)) { $response["error2"] = false; $response["message2"] = "Directory Created Succesfully"; } else { $response["error2"] = true; $response["error3"] = true; $response["error4"] = true; $response["message2"] = "Failed to create directory. Please try again"; $response["filepath"] = $filepath; } } // Images will be saved under images folder $file = fopen($filepath, 'wb'); if ($file == false) { $response["error3"] = true; $response["error4"] = true; $response["message3"] = "Failed to open file. Please try again"; $response["filepath"] = $filepath; echoRespnse(200, $response); } else { // Create File $response["error3"] = false; $response["message3"] = "File is open and good to write"; fwrite($file, $binary); fclose($file); if (file_exists($filepath)) { $response["error4"] = false; $response["message4"] = "File exists"; $response["index"] = $index; echoRespnse(201, $response); } else { $response["error4"] = true; $response["message4"] = "File doesn't exist"; echoRespnse(200, $response); } } } else { $response["error"] = true; $response["error2"] = true; $response["error3"] = true; $response["error4"] = true; $response["message"] = "Failed to update video. Please try again"; $response["id"] = $id; $response["im_path"] = $im_path; echoRespnse(200, $response); } });
- ¿Cómo fusionar un archivo de audio con una imagen o un archivo de vídeo en android?
- ¿Los reproductores multimedia por defecto para Android e iOS admiten rango de búsqueda de bytes?
- Tasa de reproducción de vídeo variable en Android
- El video de YouTube no se abre en webView
- Cómo capturar vídeo usando Intent y establecer la ruta de grabación y limitar el tiempo de grabación
- ¿Cuál es la ruta de acceso a la carpeta "assets" de tus aplicaciones en Android?
- Interceptar solicitud de fuente de video HTML5 en Android WebView
- ThumbnailUtils.createVideoThumbnail devuelve NULL al capturar un nuevo video
Después de probar una gran cantidad de cosas diferentes que encontré una manera utilizando HttpUrlConnection, aquí está la nueva versión del método syncFullVideos:
private void syncFullVideos() { new StreamFileTask().execute(); } private class StreamFileTask extends AsyncTask<Void,Integer,Void> { protected Void doInBackground(Void... params) { syncSucces2 = new boolean[syncVideos.size()]; Arrays.fill(syncSucces2, Boolean.FALSE); for (int i = 0; i < syncVideos.size(); i++) { Video video = syncVideos.get(i); String picturepath = video.getImage_path(); if (fileExists(picturepath)) { File sourceFile = new File(picturepath); String fileName = video.getDBImage_path(); int id = video.getId(); int index = syncVideos.indexOf(video); HttpURLConnection conn; DataOutputStream dos; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1024 * 1024; int serverResponseCode; Log.e("VideoUpload", "Uploading: sourcefileURI, " + fileName); if (!sourceFile.isFile()) { Log.e("uploadFile", "Source File not exist"); } else { try { FileInputStream fin = new FileInputStream(sourceFile); URL url = new URL("http://android.diggin.io/diggin/v1/vidUpload.php"); Log.v("VideoUpload", url.toString()); // Open a HTTP connection to the URL conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); // Allow Inputs conn.setDoOutput(true); // Allow Outputs conn.setUseCaches(false); // Don't use a Cached Copy conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("ENCTYPE", "multipart/form-data"); conn.setRequestProperty("Content-Type", "multipart/form-data"); conn.setRequestProperty("X_FILE_NAME", fileName); conn.setRequestProperty("VID_ID", String.valueOf(id)); conn.setRequestProperty("VID_INDEX", String.valueOf(index)); conn.setRequestProperty("CONTENT_LENGTH", String.valueOf(sourceFile.length())); publishProgress(2, i); dos = new DataOutputStream(conn.getOutputStream()); bytesAvailable = fin.available(); int thirdOfBytes = bytesAvailable / 3; int state = 0; bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; bytesRead = fin.read(buffer, 0, bufferSize); while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fin.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fin.read(buffer, 0, bufferSize); Log.i("VideoUpload", "->"); if (bytesAvailable < thirdOfBytes && state == 1) { publishProgress(4, i); state = 2; } else if (bytesAvailable < (2 * thirdOfBytes) && state == 0) { publishProgress(3, i); state = 1; } } publishProgress(5, i); serverResponseCode = conn.getResponseCode(); String serverResponseMessage = conn.getResponseMessage(); Log.i("VideoUpload", "HTTP Response is : " + serverResponseMessage + ": " + serverResponseCode); publishProgress(9, i); DataInputStream inStream; HashMap<String,String> responseMap = new HashMap<>(); try { inStream = new DataInputStream(conn.getInputStream()); String str; while ((str = inStream.readLine()) != null) { Log.e("VideoUpload", "SOF Server Response: " + str); String[] responseItem = str.split(" - "); responseMap.put(responseItem[0], responseItem[1]); } inStream.close(); if (responseMap.get("ERROR").equals("FALSE")) { int index2 = Integer.parseInt(responseMap.get("INDEX")); syncSucces2[index2] = true; Log.e("AddSucces(" + (addCount + 1) + "/" + (syncVideos.size()) + ")", video.toString()); } } catch (IOException e) { Log.e("VideoUpload", "SOF error: " + e.getMessage(), e); } fin.close(); dos.flush(); dos.close(); publishProgress(10, i); } catch (MalformedURLException ex) { ex.printStackTrace(); Log.e("VideoUpload", "UL error: " + ex.getMessage(), ex); } catch (Exception e) { e.printStackTrace(); Log.e("UploadFileException", "Exception : " + e.getMessage(), e); } } } } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); progressBar.setProgress(values[0] + (values[1] * 10)); } @Override protected void onPreExecute() { super.onPreExecute(); progressBar = new ProgressDialog(PictureActivity.this); progressBar.setMax(10 * syncVideos.size()); progressBar.setMessage("Uploading Video File(s)"); progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressBar.setCancelable(false); progressBar.show(); } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); progressBar.hide(); refreshPhotos(); } }
Y he cambiado mucho el PHP, en lugar de usar una parte de código en el archivo index.php (con la API REST) hice otro archivo llamado uploadVideo.php:
<?php require_once '../include/File_Streamer.php'; require_once '../include/DbHandler.php'; if (!isset($_SERVER['HTTP_X_FILE_NAME']) || !isset($_SERVER['HTTP_VID_ID']) || !isset($_SERVER['HTTP_VID_INDEX'])) { throw new Exception("Invalid Headers!"); } else { $im_path = $_SERVER['HTTP_X_FILE_NAME']; $id = $_SERVER['HTTP_VID_ID']; $index = $_SERVER['HTTP_VID_INDEX']; $db = new DbHandler(); $response = array(); if($db->updateVideo($id, $im_path)) { $im_path2 = explode("_",$im_path); $im_path2[0] .= 's'; $im_path2[2] = $im_path; $im_path3 = implode("/",$im_path2); $filepath = '../images/'.$im_path3; $dirpath = str_replace($im_path,"",$filepath); $ft = new File_Streamer(); $ft->setDestination(__DIR__ . '/' . $dirpath); if ($ft->receive()) { echo "ERROR - FALSE\n"; echo "MESSAGE - UPLOADED VIDEO WITH SUCCES\n"; echo "INDEX - " . $index; } else { echo "ERROR - TRUE\n"; echo "MESSAGE - FAILED TO SAVE VIDEO FILE"; } } else { echo "ERROR - TRUE\n"; echo "MESSAGE - FAILED TO ADD TO DATABASE"; } }
Y estoy usando la siguiente clase también:
<?php class File_Streamer { private $_fileName; private $_contentLength; private $_destination; public function __construct() { if (!isset($_SERVER['HTTP_X_FILE_NAME']) || !isset($_SERVER['CONTENT_LENGTH'])) { throw new Exception("Invalid Headers!"); } $this->_fileName = $_SERVER['HTTP_X_FILE_NAME']; $this->_contentLength = $_SERVER['CONTENT_LENGTH']; } public function isValid() { if (($this->_contentLength > 0)) { return true; } return false; } public function setDestination($destination) { $this->_destination = $destination; } public function receive() { try { if (!$this->isValid()) { throw new Exception('No file uploaded!'); } $fileReader = fopen('php://input', "r"); $fileWriter = fopen($this->_destination . $this->_fileName, "w+"); while(true) { $buffer = fgets($fileReader, 4096); if (strlen($buffer) == 0) { fclose($fileReader); fclose($fileWriter); return true; } fwrite($fileWriter, $buffer); } return false; } catch(Exception $ex) { echo "error: " . $ex->getMessage(); } } }
- Cómo interactuar con el cuadro de diálogo USSD mediante programación en android
- Retrofit2 ¿Datos de formulario codificados estáticos?