需求:下載視頻人灼,下載后只能用自己的APP打開,不允許傳播
實現(xiàn)思路:服務(wù)器加密顾翼,下載下來本地解密投放,由于android播放視頻需要解密完成后再播放,這樣存在安全問題适贸,所以我就搭建本地服務(wù)器灸芳,實現(xiàn)視頻加密
1.先給出build的內(nèi)容,搭建本地服務(wù)器拜姿,我使用的是AndroidAsync開源庫實現(xiàn)
compile 'com.koushikdutta.async:androidasync:2.+'
2.話不多說烙样,直接上代碼,由于是demo蕊肥,加密和解密都是本地實現(xiàn)
public class MainActivityextends AppCompatActivityimplements View.OnClickListener, NIOHttpServer.ExistenceListener {
private static final StringTAG ="MainActivity";
private VideoViewmVideoView;
private ButtonmButton1, mButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
//本地服務(wù)器啟動
new Thread(new Runnable() {
@Override
public void run() {//開一個線程防止卡住ui線程
NIOHttpServer
.getInstance(MainActivity.this)
.startServer(MainActivity.this);//本地服務(wù)器開始啟動谒获,傳入監(jiān)聽,監(jiān)聽文件是否存在
}
}).start();
}
private void init() {
mVideoView = findViewById(R.id.mVideoView);
mButton1 = findViewById(R.id.mButton1);
mButton2 = findViewById(R.id.mButton2);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.mButton1://加密
try {
//獲取assets目錄下面的文件流
InputStream inputStream = getResources().getAssets().open("test.mp4", AssetManager.ACCESS_RANDOM);
File file =new File(Environment.getExternalStorageDirectory().getPath() +"/out.zc");//加密后的文件的路徑
BufferedOutputStream out =new BufferedOutputStream(new FileOutputStream(file));
byte[] bytes1 ="qwertyuiopa".getBytes();
//開頭寫入11個干擾字符 寫入后視頻打不開
out.write(bytes1);
byte[] bytes =new byte[512];
int l;
//正常的寫入視頻源文件呢
while ((l = inputStream.read(bytes, 0, bytes.length)) != -1) {
out.write(bytes, 0, l);
}
inputStream.close();
out.close();
Log.e(TAG, "onClick: 完成了加密");
Toast.makeText(MainActivity.this, "完成了加密", Toast.LENGTH_SHORT).show();
}catch (IOException e) {
e.printStackTrace();
}
break;
case R.id.mButton2://播放
test2();
break;
}
}
private void test2() {
String code = UnicodeUtils.string2Unicode("out.zc");
Uri uri = Uri.parse("http://127.0.0.1:5000/" + code);//后面的參數(shù)必須進行Unicode編碼壁却,防止中文亂碼
//設(shè)置視頻控制器
MediaController controller =new MediaController(this);
mVideoView.setMediaController(controller);
//播放完成回調(diào)
mVideoView.setOnCompletionListener(new MyPlayerOnCompletionListener());
//設(shè)置視頻路徑
mVideoView.setVideoURI(uri);
//開始播放視頻
mVideoView.start();
mVideoView.requestFocus();
}
@Override
public void fail(String str) {//文件不存在
//todo
}
@Override
public void success() {//成功
//todo
}
class MyPlayerOnCompletionListenerimplements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
}
}
}
3.本地服務(wù)器端的實現(xiàn)
public class NIOHttpServerimplements HttpServerRequestCallback {
private static final StringTAG ="NIOHttpServer";
//當(dāng)前類的實列 采用用單列
private static NIOHttpServermInstance;
//端口
public static int PORT_LISTEN_DEFALT =5000;
//監(jiān)聽
private ExistenceListenerexistenceListener;
//服務(wù)器實列
private AsyncHttpServerserver =new AsyncHttpServer();
//單列模式
public static NIOHttpServergetInstance(Context context1) {
if (mInstance ==null) {//懶漢的寫法
// 增加類鎖,保證只初始化一次
synchronized (NIOHttpServer.class) {
if (mInstance ==null) {
mInstance =new NIOHttpServer();
}
}
}
return mInstance;
}
//返回錯誤碼枚舉
public static enum Status {
REQUEST_OK(200, "請求成功"),
REQUEST_ERROR(500, "請求失敗"),
REQUEST_ERROR_API(501, "無效的請求接口"),
REQUEST_ERROR_CMD(502, "無效命令"),
REQUEST_ERROR_DEVICEID(503, "不匹配的設(shè)備ID"),
REQUEST_ERROR_ENV(504, "不匹配的服務(wù)環(huán)境");
private final int requestStatus;
private final Stringdescription;
Status(int requestStatus, String description) {
this.requestStatus = requestStatus;
this.description = description;
}
public StringgetDescription() {
return description;
}
public int getRequestStatus() {
return requestStatus;
}
}
/**
* 開啟本地服務(wù)
*/
public void startServer(ExistenceListener existenceListener) {
//如果有其他的請求方式批狱,例如下面一行代碼的寫法
server.addAction("OPTIONS", "[\\d\\D]*", this);
server.get("[\\d\\D]*", this);
server.post("[\\d\\D]*", this);
//服務(wù)器監(jiān)聽端口
server.listen(PORT_LISTEN_DEFALT);
//傳入監(jiān)聽進來
this.existenceListener = existenceListener;
}
@Override
public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
//unicode轉(zhuǎn)換
String unicode = request.getPath().replace("/", "\\");
//獲取傳入的參數(shù)
String param = UnicodeUtils.unicode2String(unicode);
Log.d(TAG, "onRequest: " + param +" " + unicode);
//這個是獲取header參數(shù)的地方,一定要謹(jǐn)記
Multimap headers = request.getHeaders().getMultiMap();
// 獲取本地的存文件的目錄
String path = Environment.getExternalStorageDirectory().getPath();
//獲取文件路徑
String filePath = path +"/" + param; // 根據(jù)url獲取文件路徑
if (TextUtils.isEmpty(path)) {
response.send("sd卡沒有找到");
return;
}
File file =new File(filePath);
BufferedInputStream stream =null;
FileInputStream inputStream =null;
try {
if (file !=null && file.exists()) {
existenceListener.success();//存在文件
Log.d(TAG, "file path = " + file.getAbsolutePath());
//獲取本地文件的輸入流
inputStream =new FileInputStream(file);
//干擾字符 “qwertyuiopa”一共有11位
byte[] bytes =new byte[11];
//不要開頭寫入的字符
inputStream.read(bytes, 0, bytes.length);
//寫入的干擾字符
Log.d(TAG, "onRequest: " +new String(bytes));
//出去干擾字符的流
stream =new BufferedInputStream(inputStream);
//寫出沒有干擾字符的流
response.sendStream(stream, stream.available());
}else {
Log.d(TAG, "file path = " + file.getAbsolutePath() +"的資源不存在");
existenceListener.fail("資源不存在");//不存在文件
}
}catch (IOException e) {
e.printStackTrace();
}
}
interface ExistenceListener {
void fail(String str);
void success();
}
}```