Android Recording Video and Upload file to Server (PHP/Upload) |
Android Recording Video and Upload file to Server (PHP/Upload) ในหัวข้อนี้จะเป็นการประยุกต์การเขียน Android App กับ Camera Recorder เพื่อเปิดกล้องและถ่าย Video Clip โดยนำไฟล์คลิปวีดีโอที่ได้จัดเก็บลงใน Storage (SD Card) และสามารถ Upload ไฟล์ที่ได้นั้นไปจัดเก็บบน Web Server ด้วย ซึ่งเราจะใช้ชุดคำสั่งของ HttpPost ส่งค่า Post ไปยัง Web Server ผ่าน HTTP และ PHP ที่ฝั่ง Web Server จะมีหน้าที่คอยรับค่าไฟล์ที่ส่งจาก Android App ไปจัดเก็บไว้ในโฟเดอร์ที่ต้องการ ซึ่งบทความนี้จะใช้ฟังก์ชั่นหลาย ๆ ตัวเข้ามาเกี่ยวข้อง เช่น AsyncTask (ทำหน้าที่เป็น Background Process) และ ProgressDialog (แสดง Dialog แบบ Progress) เพื่อแสดงสถานะให้ User ทราบว่าในขณะนี้โปรแกรมกำลังทำงานและอัพโหลดไฟล์อยู่
Android Recording Video and Upload file to Server (PHP/Upload)
การเปิดกล้อง Camera และ Capture เป็นไฟล์ Video
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
takeVideoIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
}
หลังจากถ่ายภาพ Video แล้วจะใช้การสร้างเป็นไฟล์ .mp4 และจัดเก็บลงใน Storage SD Card
private static File getOutputMediaFile(int type) {
// Generate File Name
java.util.Date date = new java.util.Date();
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(date.getTime());
strFileName = "Clip_" + timeStamp + ".mp4";
File mediaFile;
if (type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(strSDCardPathName + strFileName);
} else {
return null;
}
return mediaFile;
}
สามารถกำหนด Path ได้จาก Environment.getExternalStorageDirectory()
static String strSDCardPathName = Environment.getExternalStorageDirectory() + "/temp_video" + "/";
การ Upload ไฟล์ไปยัง Web Server
final Button btnUpload = (Button) findViewById(R.id.btnUpload);
// Perform action on click
btnUpload.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// *** Upload file to Server
uploadFiletoServer(strSDCardPathName + strFileName, strURLUpload);
}
});
ชุดคำสั่งสำหรับการ Upload
public static boolean uploadFiletoServer(String strSDPath, String strUrlServer) {
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 1 * 1024 * 1024;
int resCode = 0;
String resMessage = "";
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
try {
File file = new File(strSDPath);
if (!file.exists()) {
return false;
}
FileInputStream fileInputStream = new FileInputStream(new File(strSDPath));
URL url = new URL(strUrlServer);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
outputStream.writeBytes(
"Content-Disposition: form-data; name=\"filUpload\";filename=\"" + strSDPath + "\"" + lineEnd);
outputStream.writeBytes(lineEnd);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// Read file
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
outputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Response Code and Message
resCode = conn.getResponseCode();
if (resCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read = 0;
while ((read = is.read()) != -1) {
bos.write(read);
}
byte[] result = bos.toByteArray();
bos.close();
resMessage = new String(result);
}
fileInputStream.close();
outputStream.flush();
outputStream.close();
return true;
} catch (Exception ex) {
// Exception handling
return false;
}
}
public static void createFolder() {
File folder = new File(strSDCardPathName);
try {
// Create folder
if (!folder.exists()) {
folder.mkdir();
}
} catch (Exception ex) {
}
}
และในฝั่งของ Web Server ซึ่งจะใช้ PHP เป็นตัวรับไฟล์
uploadFile.php (PHP)
<?php
if(@move_uploaded_file($_FILES["filUpload"]["tmp_name"],"myFolder/".$_FILES["filUpload"]["name"]))
{
$arr["StatusID"] = "1";
$arr["Error"] = "";
}
else
{
$arr["StatusID"] = "0";
$arr["Error"] = "Error cannot upload file.";
}
echo json_encode($arr);
?>
โดยจะ Upload ลงไนโฟเดอร์ myFolder
ในการเรียกใช้งาน Camera และ Save ลงใน Storage (SD Card), Internet จะต้องกำหนด Permission และ Feature ดังนี้
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Example ตัวอย่างการเขียน Android เพื่อเปิดกล้องถ่าย Video Clip และ Capture คลิปวีดีโอ และ การ Upload รูปภาพไปยัง Web Server
โครงสร้างของไฟล์ในโปรเจคสามารถใช้ได้ทั้งบน Eclipse และ Android Studio
activity_main.xml
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tableLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow
android:id="@+id/tableRow1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Camera Recorder Videos"
android:layout_span="1"
android:textAppearance="?android:attr/textAppearanceLarge" />
</TableRow>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.1"
android:orientation="horizontal" >
<VideoView
android:id="@+id/vdoView"
android:layout_width="wrap_content"
android:layout_height="214dp"
android:layout_weight="0.64" />
<Button
android:id="@+id/btnRecorder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Camera Recorder" />
<Button
android:id="@+id/btnPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play" />
<Button
android:id="@+id/btnUpload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload" />
</TableLayout>
<View
android:layout_height="1dip"
android:background="#CCCCCC" />
<LinearLayout
android:id="@+id/LinearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="By.. ThaiCreate.Com" />
</LinearLayout>
</TableLayout>
ไฟล์ Java
MainActivity.java
package com.myapp;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.view.Menu;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.VideoView;
public class MainActivity extends Activity {
VideoView vdoView;
private Uri fileUri;
public static final int MEDIA_TYPE_VIDEO = 2;
static final int REQUEST_VIDEO_CAPTURE = 1;
static String strSDCardPathName = Environment.getExternalStorageDirectory() + "/temp_video" + "/";
static String strFileName = "";
static String strURLUpload = "https://www.thaicreate.com/android/uploadFile.php";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Permission StrictMode
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
// *** Create Folder
createFolder();
// *** VideoView
vdoView = (VideoView) findViewById(R.id.vdoView);
// *** Camera Recorder
final Button btnRecorder = (Button) findViewById(R.id.btnRecorder);
// Perform action on click
btnRecorder.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
takeVideoIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
}
}
});
// *** Play
final Button btnPlay = (Button) findViewById(R.id.btnPlay);
// Perform action on click
btnPlay.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
vdoView.setVideoURI(Uri.parse(strSDCardPathName + strFileName));
vdoView.setMediaController(new MediaController(MainActivity.this));
vdoView.requestFocus();
vdoView.start();
}
});
// *** Upload Video
final Button btnUpload = (Button) findViewById(R.id.btnUpload);
// Perform action on click
btnUpload.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// *** Upload file to Server
uploadFiletoServer(strSDCardPathName + strFileName, strURLUpload);
}
});
}
private static Uri getOutputMediaFileUri(int type) {
return Uri.fromFile(getOutputMediaFile(type));
}
private static File getOutputMediaFile(int type) {
// Generate File Name
java.util.Date date = new java.util.Date();
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(date.getTime());
strFileName = "Clip_" + timeStamp + ".mp4";
File mediaFile;
if (type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(strSDCardPathName + strFileName);
} else {
return null;
}
return mediaFile;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
public static boolean uploadFiletoServer(String strSDPath, String strUrlServer) {
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 1 * 1024 * 1024;
int resCode = 0;
String resMessage = "";
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
try {
File file = new File(strSDPath);
if (!file.exists()) {
return false;
}
FileInputStream fileInputStream = new FileInputStream(new File(strSDPath));
URL url = new URL(strUrlServer);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
outputStream.writeBytes(
"Content-Disposition: form-data; name=\"filUpload\";filename=\"" + strSDPath + "\"" + lineEnd);
outputStream.writeBytes(lineEnd);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// Read file
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
outputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Response Code and Message
resCode = conn.getResponseCode();
if (resCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read = 0;
while ((read = is.read()) != -1) {
bos.write(read);
}
byte[] result = bos.toByteArray();
bos.close();
resMessage = new String(result);
}
fileInputStream.close();
outputStream.flush();
outputStream.close();
return true;
} catch (Exception ex) {
// Exception handling
return false;
}
}
public static void createFolder() {
File folder = new File(strSDCardPathName);
try {
// Create folder
if (!folder.exists()) {
folder.mkdir();
}
} catch (Exception ex) {
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Screenshot
หน้าแรกของ App ให้คลิกที่ Camera Recorder
โปรแกรมจะ Intent ไปยัง App ที่ทำหน้าที่ถ่าย Video Clip และเปิด Camera ให้ทำการคลิกที่ Recorder หรืออัดวีดีโอ
หลังจากที่ถ่ายคลิปวีดีโอเรียบร้อยแล้วให้คลิก Back กลับมายัง App เราสามารถคลิกที่ Play เพื่อเล่นไฟล์คลิป
และในกรณีที่ต้อการอัพโหลดไปยัง Web Server ให้คลิกที่ Upload
ไฟล์คลิปถูดสร้างและจัดเก็บไว้ใน Storage (SD Card)
ไฟล์จะถูก Upload มายัง Web Server ตามที่สั่ง PHP ได้จัดเก็บลงในโฟเดอร์ "myFolder"
เนื่องจากไฟล์วีดีโอนั้นมีขนาดใหญ่ เราสามารถใช้ AsyncTask และ ProgressDialog เข้ามาช่วยในการจัดการ Interface
package com.myapp;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.view.Menu;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.VideoView;
public class MainActivity extends Activity {
VideoView vdoView;
private Uri fileUri;
public static final int MEDIA_TYPE_VIDEO = 2;
static final int REQUEST_VIDEO_CAPTURE = 1;
static String strSDCardPathName = Environment.getExternalStorageDirectory() + "/temp_video" + "/";
static String strFileName = "";
static String strURLUpload = "https://www.thaicreate.com/android/uploadFile.php";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Permission StrictMode
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
// *** Create Folder
createFolder();
// *** VideoView
vdoView = (VideoView) findViewById(R.id.vdoView);
// *** Camera Recorder
final Button btnRecorder = (Button) findViewById(R.id.btnRecorder);
// Perform action on click
btnRecorder.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
takeVideoIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
}
}
});
// *** Play
final Button btnPlay = (Button) findViewById(R.id.btnPlay);
// Perform action on click
btnPlay.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
vdoView.setVideoURI(Uri.parse(strSDCardPathName + strFileName));
vdoView.setMediaController(new MediaController(MainActivity.this));
vdoView.requestFocus();
vdoView.start();
}
});
// *** Upload Video
final Button btnUpload = (Button) findViewById(R.id.btnUpload);
// Perform action on click
btnUpload.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// *** Upload file to Server
new UploadAsync(MainActivity.this).execute();
}
});
}
private static Uri getOutputMediaFileUri(int type) {
return Uri.fromFile(getOutputMediaFile(type));
}
private static File getOutputMediaFile(int type) {
// Generate File Name
java.util.Date date = new java.util.Date();
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(date.getTime());
strFileName = "Clip_" + timeStamp + ".mp4";
File mediaFile;
if (type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(strSDCardPathName + strFileName);
} else {
return null;
}
return mediaFile;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
// Upload Image in Background
public class UploadAsync extends AsyncTask<String, Void, Void> {
// ProgressDialog
private ProgressDialog mProgressDialog;
public UploadAsync(MainActivity activity) {
mProgressDialog = new ProgressDialog(activity);
mProgressDialog.setMessage("Uploading please wait.....");
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCancelable(false);
}
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog.show();
}
@Override
protected Void doInBackground(String... par) {
// *** Upload file to Server
uploadFiletoServer(strSDCardPathName + strFileName, strURLUpload);
//*** Clear Folder
clearFolder();
return null;
}
protected void onPostExecute(Void unused) {
mProgressDialog.dismiss();
}
}
public static boolean uploadFiletoServer(String strSDPath, String strUrlServer) {
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 1 * 1024 * 1024;
int resCode = 0;
String resMessage = "";
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
try {
File file = new File(strSDPath);
if (!file.exists()) {
return false;
}
FileInputStream fileInputStream = new FileInputStream(new File(strSDPath));
URL url = new URL(strUrlServer);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
outputStream.writeBytes(
"Content-Disposition: form-data; name=\"filUpload\";filename=\"" + strSDPath + "\"" + lineEnd);
outputStream.writeBytes(lineEnd);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// Read file
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
outputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Response Code and Message
resCode = conn.getResponseCode();
if (resCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read = 0;
while ((read = is.read()) != -1) {
bos.write(read);
}
byte[] result = bos.toByteArray();
bos.close();
resMessage = new String(result);
}
fileInputStream.close();
outputStream.flush();
outputStream.close();
return true;
} catch (Exception ex) {
// Exception handling
return false;
}
}
public static void createFolder() {
File folder = new File(strSDCardPathName);
try {
// Create folder
if (!folder.exists()) {
folder.mkdir();
}
} catch (Exception ex) {
}
}
public static void clearFolder(){
File dir = new File(strSDCardPathName);
if (dir.isDirectory())
{
String[] children = dir.list();
for (int n = 0; n < children.length; n++)
{
new File(dir, children[n]).delete();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
แสดง ProgressDialog ในขณะที่กำลัง Upload
และในกรณีที่ต้องการอัพโหลดไฟล์ ๆ ไฟล์พร้อมกัน
ในกรณีที่คลิปนั้นมีหลายไฟล์ และต้องการอัพโหลดพร้อม ๆ กัน สามารถแก้ไขที่ Event ของ doInBackground
@Override
protected Void doInBackground(String... par) {
// *** Upload all file to Server
File file = new File(strSDCardPathName);
File[] files = file.listFiles();
for (File sfil : files) {
if (sfil.isFile()) {
uploadFiletoServer(sfil.getAbsolutePath(), strURLUpload);
}
}
//*** Clear Folder
clearFolder();
return null;
}
ในการอัพโหลดไฟล์หลาย ๆ ไฟล์และมีขนาดใหญ่ อาจจะต้องตรวจสอบ Limit ของ Server (PHP) ด้วย เช่น max_upload_size
ไฟล์ที่ถูกอัพโหลดเข้าไปใน Web Server
|
ช่วยกันสนับสนุนรักษาเว็บไซต์ความรู้แห่งนี้ไว้ด้วยการสนับสนุน Source Code 2.0 ของทีมงานไทยครีเอท
|
|
|
By : |
ThaiCreate.Com Team (บทความเป็นลิขสิทธิ์ของเว็บไทยครีเอทห้ามนำเผยแพร่ ณ เว็บไซต์อื่น ๆ) |
|
Score Rating : |
|
|
|
Create/Update Date : |
2015-11-16 16:15:43 /
2017-03-26 21:12:50 |
|
Download : |
No files |
|
Sponsored Links / Related |
|
|
|
|
|
|
|