├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── line
│ │ └── hee
│ │ └── linemediaplayer
│ │ ├── DemoActivity.java
│ │ ├── MainApplication.java
│ │ └── Util.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_action_name.png
│ ├── drawable-mdpi
│ └── ic_action_name.png
│ ├── drawable-xhdpi
│ ├── ic_action_name.png
│ └── music_seek_thumb_ico_blue.png
│ ├── drawable-xxhdpi
│ ├── ic_action_name.png
│ ├── loading_ico.png
│ ├── loading_ico_1.png
│ ├── loading_ico_2.png
│ ├── loading_ico_3.png
│ └── music_seek_thumb_ico_blue.png
│ ├── drawable
│ ├── loading_drawer.xml
│ ├── play_state_selector.xml
│ └── seek_loading_drawer.xml
│ ├── layout
│ ├── activity_demo.xml
│ └── view_demo_play.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── ids.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── player
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── line
│ │ └── hee
│ │ └── library
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── line
│ │ │ └── hee
│ │ │ └── library
│ │ │ ├── PlayConfigature.java
│ │ │ ├── SocketProxyPlay.java
│ │ │ ├── StorageUtils.java
│ │ │ └── tools
│ │ │ └── L.java
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── line
│ └── hee
│ └── library
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | /.gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #一个简单的本地代理播放器
2 | * [介绍](#1)
3 | * [使用方法](#2)
4 |
5 |
6 | ----------
7 | ## 介绍
8 |
9 | > 由于android原本的播放器MediaPlayer并不支持音视频缓存控制,而且MediaPlayer 中无法获取到流媒体数据,但是我恰好需要缓存这个流媒体到本地,并且第二次打开此媒体的时候直接读取缓存呢?所以这里我参考了网上的解决方案,使用ServerSocket 进行本地代理,播放的时候让播放器发起一个本地的媒体源获取,然后通过代理我们取到此client请求,并且重新组装以后发送远程服务器请求真正的流媒体数据,这时候我们就可以取到真正的数据并且进行缓存,以便第二次使用的时候直接读取缓存。
10 | 模拟的时候进行了302重定向处理,可以递归定向到真实的数据流地址。
11 |
12 | ## 使用方法
13 | > 建议在Application 处使用此方法进行初始化,这里会初始化一个文件夹作为缓存路径:
14 | > `SocketProxyPlay.getInstance().init(this, true);`
15 | > 其中,第一个参数为上下文,第二个参数为是否开启本地监听,如果这里为false,那么在需要使用的时候需要调用`SocketProxyPlay.getInstance().listening()`方进行监听开启,此方法建议和`close()`方进行对应使用,一般在Activity的onCreate 开启监听, 在 onDestory 进行close关闭监听。
16 | >
17 | > 如果在Android 6.0 之上操作系统中,使用sd权限,需要进行权限申请,建议开启一个过渡的Activity(比如欢迎页)进行权限申请,申请结束后在申请结束的回调` onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) `里使用 `SocketProxyPlay.getInstance().createDefaultSavePath(Context context)`方法进行再次的缓存路径确认,以确保路径真的是sd卡路径,以免占用太多手机存储空间。
18 | >
19 | > 具体可以参考DemoActivity 里的写法进行使用。
20 |
21 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "24.0.0"
6 | defaultConfig {
7 | applicationId "line.hee.linemediaplayer"
8 | minSdkVersion 16
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:23.4.0'
28 | testCompile 'junit:junit:4.12'
29 | compile project(':player')
30 | }
31 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\developer\eclipse-standard-luna-SR2-win32\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/line/hee/linemediaplayer/DemoActivity.java:
--------------------------------------------------------------------------------
1 | package line.hee.linemediaplayer;
2 |
3 | import android.graphics.drawable.AnimationDrawable;
4 | import android.graphics.drawable.Drawable;
5 | import android.graphics.drawable.StateListDrawable;
6 | import android.media.MediaPlayer;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.os.Message;
10 | import android.support.annotation.NonNull;
11 | import android.support.annotation.Nullable;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.text.TextUtils;
14 | import android.util.Log;
15 | import android.view.View;
16 | import android.widget.SeekBar;
17 | import android.widget.TextView;
18 | import android.widget.Toast;
19 |
20 | import java.text.SimpleDateFormat;
21 |
22 | import line.hee.library.SocketProxyPlay;
23 | import line.hee.library.StorageUtils;
24 |
25 | /**
26 | * Created by hacceee on 2017/1/24.
27 | */
28 |
29 | public class DemoActivity extends AppCompatActivity implements View.OnClickListener, SeekBar.OnSeekBarChangeListener{
30 |
31 | private static String TAG = "DemoActivity";
32 |
33 | private static String[] PlayUrls = new String[]{"http://mp3-cdn.luoo.net/low/luoo/radio889/01.mp3 ",
34 | "http://mp3-cdn.luoo.net/low/luoo/radio889/02.mp3 ",
35 | "http://mp3-cdn.luoo.net/low/luoo/radio889/03.mp3",
36 | "http://mp3-cdn.luoo.net/low/luoo/radio889/04.mp3",
37 | "http://mp3-cdn.luoo.net/low/luoo/radio889/05.mp3",
38 | "http://mp3-cdn.luoo.net/low/luoo/radio889/06.mp3",
39 | "http://mp3-cdn.luoo.net/low/luoo/radio889/07.mp3",
40 | "http://mp3-cdn.luoo.net/low/luoo/radio889/08.mp3",
41 | "http://mp3-cdn.luoo.net/low/luoo/radio889/09.mp3",
42 | "http://mp3-cdn.luoo.net/low/luoo/radio889/10.mp3"};
43 |
44 | // private SimpleDateFormat mProgressFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
45 |
46 | private int mPlayingIndex = 0;
47 |
48 |
49 | SeekBar mSeekBar;
50 | MediaPlayer mMediaPlayer;
51 | private String mPreparedUrl;
52 |
53 | private View mPlayBtn;
54 |
55 | TextView mTitle;
56 |
57 | @Override
58 | protected void onCreate(@Nullable Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | Util.checkAndRequestStoragePermission(this);
61 | setContentView(R.layout.activity_demo);
62 | mSeekBar = (SeekBar)findViewById(R.id.seekBar2);
63 | mSeekBar.setOnSeekBarChangeListener(this);
64 | findViewById(R.id.previousPlay).setOnClickListener(this);
65 | findViewById(R.id.nextPlay).setOnClickListener(this);
66 | mPlayBtn = findViewById(R.id.play);
67 | mPlayBtn.setOnClickListener(this);
68 | mTitle = (TextView) findViewById(R.id.title);
69 | mPreparedUrl = PlayUrls[0];
70 | mTitle.setText(mPreparedUrl);
71 | initMediaPlayer();
72 |
73 | }
74 |
75 | /**
76 | * 初始化播放器
77 | */
78 | private void initMediaPlayer(){
79 | mMediaPlayer = new MediaPlayer();
80 | mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
81 | @Override
82 | public void onPrepared(MediaPlayer mp) {
83 |
84 | if(mPlayBtn.isSelected()) {
85 | Log.d(TAG, "prepare ok");
86 | mMediaPlayer.start();
87 | mHander.removeCallbacks(mProgressRun);
88 | mHander.post(mProgressRun);
89 | }
90 | mSeekBar.setSelected(false);
91 | mPreparedUrl = null;
92 | }
93 | });
94 |
95 | mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
96 | @Override
97 | public void onCompletion(MediaPlayer mp) {
98 |
99 | }
100 | });
101 |
102 | mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
103 | @Override
104 | public boolean onError(MediaPlayer mp, int what, int extra) {
105 | Toast.makeText(DemoActivity.this, "播放失败。", Toast.LENGTH_SHORT).show();
106 | stop();
107 | return false;
108 | }
109 | });
110 | }
111 |
112 | private void resetPlay(String url){
113 | stop();
114 | mPreparedUrl = url;
115 | SocketProxyPlay.getInstance().play(url, mMediaPlayer);
116 | mSeekBar.setSelected(true);
117 | Drawable drawable = mSeekBar.getThumb().getCurrent();
118 | if(drawable instanceof AnimationDrawable){
119 | ((AnimationDrawable)drawable).start();
120 | }
121 |
122 | }
123 |
124 | private void stop(){
125 | if(mMediaPlayer != null){
126 | mMediaPlayer.stop();
127 | mMediaPlayer.reset();
128 | }
129 | }
130 |
131 |
132 | @Override
133 | public void onClick(View v) {
134 | switch (v.getId()){
135 | case R.id.play:
136 | v.setSelected(!v.isSelected());
137 | if(v.isSelected()){
138 | if(!TextUtils.isEmpty(mPreparedUrl)) {
139 | resetPlay(mPreparedUrl);
140 | }else{
141 | mMediaPlayer.start();
142 | }
143 | }else if(mMediaPlayer.isPlaying()){
144 | mMediaPlayer.pause();
145 | }
146 | break;
147 | case R.id.nextPlay:
148 | if(mPlayingIndex >= PlayUrls.length - 1){
149 | mPlayingIndex = 0;
150 | }else{
151 | mPlayingIndex += 1;
152 | }
153 | String url = PlayUrls[mPlayingIndex];
154 | mPlayBtn.setSelected(true);
155 | resetPlay(url);
156 | mTitle.setText(url);
157 | break;
158 | case R.id.previousPlay:
159 | if(mPlayingIndex <= 0){
160 | mPlayingIndex = PlayUrls.length - 1;
161 | }else{
162 | mPlayingIndex -= 1;
163 | }
164 | mPlayBtn.setSelected(true);
165 | String pUrl = PlayUrls[mPlayingIndex];
166 | resetPlay(pUrl);
167 | mTitle.setText(pUrl);
168 | break;
169 | }
170 | }
171 |
172 | @Override
173 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
174 |
175 | }
176 |
177 | @Override
178 | public void onStartTrackingTouch(SeekBar seekBar) {
179 | seekBar.setTag(R.id.tag_perversion_progress, seekBar.getProgress());
180 | }
181 |
182 | @Override
183 | public void onStopTrackingTouch(SeekBar seekBar) {
184 | if(seekBar.isSelected()){
185 | seekBar.setProgress((Integer) seekBar.getTag(R.id.tag_perversion_progress));
186 | return;
187 | }
188 | int durationTotal = mMediaPlayer.getDuration();
189 | int percent = (int) ((double) seekBar.getProgress() / 100 * durationTotal);
190 | mMediaPlayer.seekTo(percent);
191 |
192 | }
193 |
194 | @Override
195 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
196 | if(requestCode == 1){
197 | SocketProxyPlay.getInstance().createDefaultSavePath(this);
198 | }
199 | }
200 |
201 | private Runnable mProgressRun = new Runnable() {
202 | @Override
203 | public void run() {
204 | if(mMediaPlayer.isPlaying()) {
205 | int percent = (int)(100 * ((double)mMediaPlayer.getCurrentPosition() / mMediaPlayer.getDuration()));
206 | mSeekBar.setProgress(percent);
207 | mHander.postDelayed(this, 100);
208 | }
209 | }
210 | };
211 |
212 | private final Handler mHander = new Handler(){
213 | @Override
214 | public void handleMessage(Message msg) {
215 | super.handleMessage(msg);
216 | }
217 | };
218 | }
219 |
--------------------------------------------------------------------------------
/app/src/main/java/line/hee/linemediaplayer/MainApplication.java:
--------------------------------------------------------------------------------
1 | package line.hee.linemediaplayer;
2 |
3 | import android.app.Application;
4 | import android.os.Environment;
5 |
6 | import line.hee.library.PlayConfigature;
7 | import line.hee.library.SocketProxyPlay;
8 | import line.hee.library.StorageUtils;
9 | import line.hee.library.tools.L;
10 |
11 | /**
12 | * Created by Administrator on 2017/1/16.
13 | */
14 |
15 | public class MainApplication extends Application {
16 |
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | L.writeLogs(true);
21 | SocketProxyPlay.getInstance().init(this, true);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/line/hee/linemediaplayer/Util.java:
--------------------------------------------------------------------------------
1 | package line.hee.linemediaplayer;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 |
8 | import line.hee.library.StorageUtils;
9 |
10 | /**
11 | * Created by Administrator on 2017/1/17.
12 | */
13 |
14 | public class Util {
15 |
16 | private static String[] PERMISSIONS_STORAGE = {
17 | android.Manifest.permission.READ_EXTERNAL_STORAGE,
18 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE };
19 | private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
20 | public static final int REQUEST_EXTERNAL_STORAGE = 1;
21 |
22 | public static void checkAndRequestStoragePermission(Activity a){
23 | if(!hasExternalStoragePermission(a) && Build.VERSION.SDK_INT >= 23){
24 | a.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
25 |
26 | }
27 | }
28 |
29 | private static boolean hasExternalStoragePermission(Context context) {
30 | int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
31 | return perm == PackageManager.PERMISSION_GRANTED;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-hdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-mdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/music_seek_thumb_ico_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xhdpi/music_seek_thumb_ico_blue.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/loading_ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/loading_ico.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/loading_ico_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/loading_ico_1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/loading_ico_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/loading_ico_2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/loading_ico_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/loading_ico_3.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/music_seek_thumb_ico_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/drawable-xxhdpi/music_seek_thumb_ico_blue.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/loading_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_state_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seek_loading_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
21 |
28 |
29 |
40 |
41 |
47 |
53 |
59 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_demo_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
15 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LineMediaPlayer
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackereee/LineMediaPlayer/c1f98cdab491de7fd46e916dd4dd74898d2d3823/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/player/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/player/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "24.0.0"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:23.4.0'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/player/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\developer\eclipse-standard-luna-SR2-win32\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/player/src/androidTest/java/line/hee/library/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package line.hee.library;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("line.hee.library.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/player/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/player/src/main/java/line/hee/library/PlayConfigature.java:
--------------------------------------------------------------------------------
1 | package line.hee.library;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created by Administrator on 2017/1/9.
7 | */
8 |
9 | public class PlayConfigature {
10 |
11 | public static void init(Context context){
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/player/src/main/java/line/hee/library/SocketProxyPlay.java:
--------------------------------------------------------------------------------
1 | package line.hee.library;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.os.Handler;
6 | import android.os.Message;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import android.text.TextUtils;
10 | import android.util.Base64;
11 |
12 | import java.io.File;
13 | import java.io.FileInputStream;
14 | import java.io.FileNotFoundException;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.io.OutputStream;
19 | import java.lang.ref.WeakReference;
20 | import java.net.InetAddress;
21 | import java.net.InetSocketAddress;
22 | import java.net.ServerSocket;
23 | import java.net.Socket;
24 | import java.net.UnknownHostException;
25 | import java.util.concurrent.ThreadFactory;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 | import line.hee.library.tools.L;
30 |
31 |
32 | /**
33 | * Created by hacceee on 2017/1/9.
34 | * 如果要mediaplayer去播放实现预加载和边下边播的话,我们要手动实现一个代理服务器,代理到本地,也就是127.0.0.1
35 | * 然后mediaplayer去访问的时候就直接请求我们的本地代理服务器
36 | * ,然后使用另外一个socket(或者http)模拟mediaplayer发送数据,返回的结果手动设置到监听到的代理client socket中
37 | */
38 |
39 | public class SocketProxyPlay {
40 |
41 | private static String TAG = "SocketProxyPlay";
42 |
43 | private static final int DEFAULT_POOL_SIZE = 3;
44 |
45 | private static final String PROXY_HOST = "127.0.0.1";
46 | private static final int PROXY_SERVER_TIME_OUT = 15 * 1000;
47 | private static final int PROXY_PORT = 8123;
48 | // private HostInfo mProxyHost;
49 | private ServerSocket mProxyServerSocket;
50 | // private Socket mRemoteSocket;
51 | // private MediaPlayer mMediaPlayer;
52 |
53 | //持有applicationContext 的变量,防止泄露内存
54 | // private Context mAppContext;
55 |
56 | private ListeningRequest mListeningRequest;
57 |
58 | private Socket mThisClient;
59 |
60 | private File mSavePlayDir;
61 |
62 | private static SocketProxyPlay mInstance;
63 |
64 | // private static Executor mProxyClientExecutors;
65 | // private static BlockingQueue proxyClientQueue = new
66 | // LinkedBlockingQueue<>();
67 |
68 | // private static BlockingQueue mString =
69 |
70 | private Thread mThread;
71 |
72 | public static SocketProxyPlay getInstance() {
73 | L.d(TAG, "socket play instance:" + mInstance);
74 | if(mInstance == null){
75 | synchronized (SocketProxyPlay.class) {
76 | if (mInstance == null) {
77 | mInstance = new SocketProxyPlay();
78 | }
79 | }
80 | }
81 | return mInstance;
82 |
83 | }
84 |
85 | public void listening() {
86 | if (mThread == null || mThread.isInterrupted()) {
87 | mThread = null;
88 | mListeningRequest = null;
89 | reset();
90 | }
91 | }
92 |
93 | private void reset(){
94 | mListeningRequest = new ListeningRequest();
95 | mThread = new Thread(mListeningRequest);
96 | mThread.start();
97 | }
98 |
99 | private SocketProxyPlay() {
100 | }
101 |
102 | public void init(Context context, boolean listen) {
103 | // mProxyHost = new HostInfo(PROXY_HOST, 80);
104 | // mAppContext = context.getApplicationContext();
105 | if(listen){
106 | listening();
107 | }
108 | createDefaultSavePath(context);
109 | }
110 |
111 | // public void setMdiaPlayer(MediaPlayer m){
112 | // this.mMediaPlayer = m;
113 | // }
114 |
115 | /**
116 | * 如果是http的请求,直接代理掉
117 | *
118 | * @param url
119 | */
120 | public void play(String url, @NonNull MediaPlayer player) {
121 | L.d( "play url:" + url);
122 | if (matchHttp(url)) {
123 | try {
124 | L.d( "url:" + url + "to proxy");
125 | mListeningRequest.mUrl = url;
126 | player.setDataSource("http://" + PROXY_HOST + ":" + PROXY_PORT);
127 | player.prepareAsync();
128 | } catch (IOException e) {
129 | e.printStackTrace();
130 | }
131 | } else {
132 | try {
133 | player.setDataSource(url);
134 | player.prepareAsync();
135 | } catch (IOException e) {
136 | e.printStackTrace();
137 | }
138 | }
139 | }
140 |
141 | class ListeningRequest implements Runnable {
142 |
143 | private String mUrl;
144 | private boolean mSocketNeedClose = false;
145 |
146 | ListeningRequest() {
147 | }
148 |
149 | @Override
150 | public void run() {
151 | if (mProxyServerSocket == null || mProxyServerSocket.isClosed()) {
152 | // 初始化
153 | try {
154 | /*
155 | * 代理服务器建立,监听mediaplayer的请求
156 | */
157 | mProxyServerSocket = new ServerSocket(PROXY_PORT, 0,
158 | InetAddress.getByName(PROXY_HOST));
159 | mProxyServerSocket.setSoTimeout(PROXY_SERVER_TIME_OUT);
160 | } catch (UnknownHostException e) {
161 | e.printStackTrace();
162 | } catch (IOException e) {
163 | L.e( "you are init proxy socket failed:" + e.getMessage());
164 | return;
165 | }
166 | }
167 | InputStream cacheIs = null;
168 | Socket client = null;
169 | OutputStream clientOs = null;
170 | while (!Thread.currentThread().isInterrupted() && !mSocketNeedClose) {
171 | try {
172 | client = mProxyServerSocket.accept();
173 | L.d( "accept request start, client:" + client);
174 | mHandler.sendEmptyMessage(PLAY_PREPARE);
175 | if (!matchHttp(mUrl)) {
176 | String errMsg = "you display is not an right url!";
177 | mHandler.obtainMessage(PLAY_FALIED, errMsg)
178 | .sendToTarget();
179 | return;
180 | }
181 | if (client == null) {
182 | mHandler.sendEmptyMessage(PLAY_FALIED);
183 | return;
184 | }
185 | mThisClient = client;
186 | // System.setProperty("http.keepAlive", "false");
187 | HostInfo remoteHost = convertRequest(client, mUrl);
188 | // 由于converRequest执行完毕若是Range的file就被删除了,所以在这个方法执行完毕之后,需要读取缓存,如果缓存还在,那么说明这里缓存是整块的,可以直接返回
189 | // 先读取缓存
190 | cacheIs = readCache(mUrl);
191 | L.d( "read cache start");
192 | if (cacheIs != null) {
193 | clientOs = client.getOutputStream();
194 | int readByte = -1;
195 | byte[] buf = new byte[1024];
196 | int total = cacheIs.available();
197 | int readLenght = 0;
198 | while ((readByte = cacheIs.read(buf, 0, buf.length)) != -1) {
199 | clientOs.write(buf, 0, readByte);
200 | readLenght += readByte;
201 | int[] readP = new int[2];
202 | readP[0] = total;
203 | readP[1] = readLenght;
204 | L.d( "read cache length:" + readLenght + "...total:"
205 | + total);
206 | mHandler.obtainMessage(PLAY_LOADING, readP)
207 | .sendToTarget();
208 | }
209 | L.d( "read cache ok");
210 | clientOs.flush();
211 | clientOs.close();
212 | cacheIs.close();
213 | client.close();
214 | mHandler.sendEmptyMessage(PLAY_COMPLETE);
215 |
216 | } else {
217 | Socket remoteSocket = sendRemoteRequest(remoteHost);
218 | resRequest(client, remoteSocket, remoteHost);
219 | }
220 | } catch (IOException e) {
221 | L.e( "listeneing url exception:" + e.getMessage());
222 | // } catch (InterruptedException e) {
223 | // L.e( "take url InterruptedException:" + e.getMessage());
224 | // e.printStackTrace();
225 | } finally {
226 | if (cacheIs != null) {
227 | try {
228 | cacheIs.close();
229 | } catch (IOException e) {
230 | e.printStackTrace();
231 | }
232 | }
233 | if (client != null && !client.isClosed()) {
234 | try {
235 | client.close();
236 | } catch (IOException e) {
237 | e.printStackTrace();
238 | }
239 | }
240 |
241 | }
242 | }
243 | try {
244 | if(mProxyServerSocket != null){
245 | mProxyServerSocket.close();
246 | mProxyServerSocket = null;
247 | }
248 | } catch (IOException e) {
249 | e.printStackTrace();
250 | }
251 |
252 | }
253 | }
254 |
255 | /**
256 | * @author hacceee 获取到给代理服务器里的请求内容,然后转到真实的socket上进行转发请求
257 | */
258 | public HostInfo convertRequest(Socket client, String remoteHost) {
259 | L.d( "convertRequest execute");
260 | byte[] buffer = new byte[512];
261 | HostInfo remoteHostInfo = null;
262 | int readByte = -1;
263 | String params = null;
264 | InputStream clientInputStream = null;
265 | try {
266 | clientInputStream = client.getInputStream();
267 | while (true) {
268 | readByte = clientInputStream.read(buffer);
269 | if (readByte == -1) {
270 | break;
271 | }
272 | if (params == null) {
273 | params = "";
274 | }
275 |
276 | params += new String(buffer, "UTF-8");
277 | if (params.toString().contains("GET")
278 | && params.contains("\r\n\r\n")) {
279 | if (remoteHostInfo == null) {
280 | remoteHostInfo = new HostInfo(remoteHost);
281 | }
282 | Pattern p = Pattern
283 | .compile("(GET)(((?!HTTP/1.1).)*)((HTTP/1.1)?\\r\\n)[\\d\\D]*");
284 | Matcher m = p.matcher(params);
285 | if (m.matches()) {
286 | params = params.replace(m.group(2), " " + remoteHost
287 | + " ");
288 | }
289 | params = params.replaceAll(PROXY_HOST + ":" + PROXY_PORT,
290 | remoteHostInfo.ip + ":" + remoteHostInfo.port);
291 | if (params.contains("Range")) {
292 | L.d( "Range to delete file:" + params);
293 | // 删除缓存
294 | File cacheFile = getCacheFile(remoteHostInfo.url);
295 | if (cacheFile != null && cacheFile.exists()) {
296 | cacheFile.delete();
297 | }
298 | remoteHostInfo.allowCache = false;
299 | } else {
300 | remoteHostInfo.allowCache = true;
301 | }
302 | remoteHostInfo.requestParams = params;
303 | break;
304 | }
305 |
306 | }
307 |
308 | } catch (IOException e) {
309 | L.e( e.getMessage());
310 | } catch (Exception e) {
311 | L.e( e.getMessage());
312 | } finally {
313 | // try {
314 | // if(clientInputStream != null){
315 | // clientInputStream.close();
316 | // }
317 | // }catch (IOException e) {
318 | // e.printStackTrace();
319 | // }
320 | }
321 | return remoteHostInfo;
322 |
323 | }
324 |
325 | /**
326 | * 将模拟mediaplayer的请求返回数据写入代理服务器,返回给mediaplayer
327 | */
328 | private void resRequest(Socket client, Socket remoteSocket,
329 | HostInfo remoteHostInfo) {
330 | L.d( "resRequest start");
331 | InputStream remoteIs = null;
332 | OutputStream clientOs = null;
333 | FileOutputStream fileOutputStream = null;
334 | try {
335 | remoteIs = remoteSocket.getInputStream();
336 | if (remoteIs == null) {
337 | return;
338 | }
339 | if (mThisClient == null || mThisClient != client) {
340 | return;
341 | }
342 | clientOs = client.getOutputStream();
343 | byte[] buf = new byte[1024];
344 | int readByteLength = -1;
345 | int writeStreamLength = 0;
346 | int total = remoteIs.available();
347 | while ((readByteLength = remoteIs.read(buf, 0, buf.length)) != -1) {
348 | writeStreamLength += readByteLength;
349 | clientOs.write(buf, 0, readByteLength);
350 | clientOs.flush();
351 | int[] writeB = new int[2];
352 | writeB[0] = total;
353 | writeB[1] = writeStreamLength;
354 | mHandler.obtainMessage(PLAY_LOADING, writeB).sendToTarget();
355 | if (remoteHostInfo.allowCache) {
356 | if (fileOutputStream == null) {
357 | File cacheFile = getCacheFile(remoteHostInfo.url);
358 | if (cacheFile != null) {
359 | if (!cacheFile.exists()) {
360 | cacheFile.createNewFile();
361 | }
362 | fileOutputStream = new FileOutputStream(cacheFile);
363 | }
364 |
365 | }
366 | fileOutputStream.write(buf, 0, readByteLength);
367 | }
368 | if (readByteLength < buf.length) {
369 | String redirectStr = new String(buf);
370 |
371 | L.d( "reponse stream :"+ redirectStr);
372 | if (redirectStr.contains("HTTP")) {
373 | int responseCode = getResponseCode(redirectStr);
374 | if (responseCode >= 300) {
375 | remoteIs.close();
376 | remoteSocket.close();
377 | if(fileOutputStream != null){
378 | fileOutputStream.flush();
379 | fileOutputStream.close();
380 | }
381 | delCache(remoteHostInfo.url);
382 |
383 | // 若遇到302重定向,递归直到遇到真正的请求地址
384 | if ((responseCode == 302 || redirectStr
385 | .contains("302"))
386 | && redirectStr.contains("Location:")) {
387 | L.d( "redirect url:" + redirectStr);
388 | // 关闭之前打开的流和socket并且删除之前的缓存
389 | int subStart = redirectStr.indexOf("Location:")
390 | + "Location:".length();
391 | String url = redirectStr.substring(subStart,
392 | redirectStr.indexOf("\r\n", redirectStr
393 | .indexOf("Location:")));
394 | HostInfo hostInfo = new HostInfo(url);
395 | hostInfo.requestParams = remoteHostInfo.requestParams;
396 | Pattern p = Pattern
397 | .compile("(GET)(((?!HTTP/1.\\d).)*)((HTTP/1.\\d)?\\r\\n)[\\d\\D]*");
398 | Matcher m = p.matcher(hostInfo.requestParams);
399 | if (m.matches()) {
400 | hostInfo.requestParams = hostInfo.requestParams
401 | .replace(m.group(2), " " + url
402 | + " ");
403 | }
404 | Socket rso = sendRemoteRequest(hostInfo);
405 | if (rso != null) {
406 | resRequest(client, rso, remoteHostInfo);
407 | return;
408 | }
409 |
410 | }
411 | client.close();
412 | clientOs.close();
413 | return;
414 | }
415 | }
416 | }
417 |
418 |
419 |
420 | }
421 | mHandler.sendEmptyMessage(PLAY_COMPLETE);
422 | } catch (IOException e) {
423 | remoteHostInfo.allowCache = false;
424 | delCache(remoteHostInfo.url);
425 | L.e( "resRequest falied! IOException:" + e.getMessage());
426 | } catch (Exception e) {
427 | L.e( "resRequest failed Exception");
428 | remoteHostInfo.allowCache = false;
429 | delCache(remoteHostInfo.url);
430 | } finally {
431 | try {
432 | if (remoteIs != null) {
433 | remoteIs.close();
434 | }
435 | if (clientOs != null) {
436 | clientOs.flush();
437 | clientOs.close();
438 | }
439 | if (fileOutputStream != null) {
440 | fileOutputStream.flush();
441 | fileOutputStream.close();
442 | }
443 | client.close();
444 | remoteSocket.close();
445 | } catch (Exception e) {
446 |
447 | }
448 | }
449 | }
450 |
451 | /**
452 | * 模拟mediaplayer 发送请求
453 | *
454 | * @param remoteInfo
455 | * @return
456 | */
457 | private Socket sendRemoteRequest(HostInfo remoteInfo) {
458 | Socket remoteSocket = null;
459 | try {
460 | remoteSocket = new Socket();
461 | remoteSocket.connect(new InetSocketAddress(remoteInfo.ip,
462 | remoteInfo.port));
463 | remoteSocket.getOutputStream().write(
464 | remoteInfo.requestParams.getBytes("UTF-8"));
465 | remoteSocket.getOutputStream().flush();
466 | } catch (IOException e) {
467 | e.printStackTrace();
468 | }
469 | return remoteSocket;
470 | }
471 |
472 | /**
473 | * 读缓存
474 | *
475 | * @param
476 | * @return
477 | */
478 | @Nullable
479 | private InputStream readCache(String url) {
480 | if (mSavePlayDir == null) {
481 | throw new NullPointerException("you must be init at first");
482 | }
483 | File file = getCacheFile(url);
484 | if (!file.exists()) {
485 | return null;
486 | }
487 | FileInputStream fileIs = null;
488 | try {
489 | fileIs = new FileInputStream(file);
490 | } catch (FileNotFoundException e) {
491 | e.printStackTrace();
492 | }
493 | return fileIs;
494 | }
495 |
496 | private File getCacheFile(String url) {
497 | String name = createFileName(url);
498 | return new File(mSavePlayDir, name);
499 | }
500 |
501 | public static String createFileName(String url) {
502 | String name = Base64.encodeToString(url.getBytes(), Base64.DEFAULT).replaceAll("/", "");
503 | return name;
504 | }
505 |
506 | private boolean matchHttp(String url) {
507 | Pattern pattern = Pattern
508 | .compile("^((https|http|ftp|rtsp|mms)?:\\/\\/)[^\\s]+");
509 | return pattern.matcher(url).find();
510 | }
511 |
512 | public static int matchPort(String url) {
513 | int port = -1;
514 | if (TextUtils.isEmpty(url)) {
515 | return port;
516 | }
517 | Pattern pattern = Pattern.compile("^:[\\d]+$");
518 | Matcher matcher = pattern.matcher(url);
519 | if (matcher.find()) {
520 | port = Integer.valueOf(pattern.toString().substring(
521 | matcher.start() + 1, matcher.end()));
522 | }
523 | return port;
524 | }
525 |
526 | public static String matchHost(String url) {
527 | Pattern pattern = Pattern.compile("[\\d\\D]?(http[s]?://)?([\\d|\\.|\\w]+)((:[\\d]+)?)/");
528 | Matcher matcher = pattern.matcher(url);
529 | String host = url;
530 | if (matcher.find()) {
531 | int start = 0;
532 | Pattern httpf = Pattern.compile("[\\d\\D]?http[s]?://");
533 | Matcher m = httpf.matcher(url);
534 | if (m.find()) {
535 | start = m.end();
536 | }
537 | int end = matcher.end() - 1;
538 |
539 | pattern = Pattern.compile(":[\\d]+");
540 | matcher = pattern.matcher(matcher.group());
541 | if (matcher.find()) {
542 | end = matcher.start();
543 | }
544 | host = url.substring(start, end);
545 | }
546 | return host;
547 |
548 | }
549 |
550 | /**
551 | * 从返回的信息中抓取返回的状态码
552 | *
553 | * @author hacceee
554 | * @return
555 | */
556 | public static int getResponseCode(String resMessage) {
557 | if (TextUtils.isEmpty(resMessage)) {
558 | return 10086;
559 | }
560 | Pattern p = Pattern
561 | .compile("^([\\s\\w\\d\\./]+[\\s]+)([\\d]+)([\\s|\\w]+\\r\\n)");
562 | Matcher matcher = p.matcher(resMessage);
563 | if (matcher.find()) {
564 | return Integer.valueOf(matcher.group(2));
565 | }
566 | return 10086;
567 | }
568 |
569 | /**
570 | * 存储相应请求相关的地址和端口及其他信息的Model
571 | */
572 | public static class HostInfo {
573 |
574 | public HostInfo(String ip, int port) {
575 | this.ip = ip;
576 | this.port = port;
577 | }
578 |
579 | public HostInfo(String url) {
580 | this.url = url;
581 | ip = matchHost(url);
582 | port = matchPort(url);
583 | if (port <= 0) {
584 | port = 80;
585 | }
586 | // fileName = createFileName(url);
587 | }
588 |
589 | public String url;
590 | public String ip;
591 | public int port;
592 | public String requestParams;
593 | public boolean allowCache = false;
594 | // public String fileName;
595 | }
596 |
597 | // public interface OnReadMediaListener{
598 | // void onPrepare();
599 | // void onLoading(long readBytes, long totalBytes);
600 | // void onLoadComplete();
601 | // void onLoadFailed(String errMsg);
602 | // }
603 |
604 | // private static List mediaListeners = new
605 | // ArrayList<>();
606 | //
607 | // public static void addReadMediaListener(OnReadMediaListener listener){
608 | // mediaListeners.add(listener);
609 | // }
610 | //
611 | // public static void removeMediaListener(OnReadMediaListener listener){
612 | // mediaListeners.remove(listener);
613 | // }
614 |
615 | // private OnReadMediaListener mReadMediaListener;
616 | // public void setReadMediaListener(OnReadMediaListener listener){
617 | // mReadMediaListener = listener;
618 | // }
619 |
620 | // public static void init(Context context){
621 | // mProxyClientExecutors = new ThreadPoolExecutor(DEFAULT_POOL_SIZE, 256,
622 | // 600, TimeUnit.SECONDS, proxyClientQueue, new ProxyClientFactory());
623 | // }
624 |
625 | public static class ProxyClientFactory implements ThreadFactory {
626 |
627 | @Override
628 | public Thread newThread(Runnable r) {
629 | return new Thread(r);
630 | }
631 | }
632 |
633 | public void createDefaultSavePath(Context context) {
634 | mSavePlayDir = new File(StorageUtils.getCacheDirectory(context, true),
635 | "/proxyMedia");
636 | if (!mSavePlayDir.exists()) {
637 | mSavePlayDir.mkdir();
638 | }
639 | }
640 |
641 | public void setCacheDir(String dir) {
642 | mSavePlayDir = new File(dir + "/proxyMedia");
643 | }
644 |
645 | public File getCachePath(){
646 | return mSavePlayDir;
647 | }
648 |
649 | private void delCache(String url) {
650 | L.d( "delCache start");
651 | File cacheFile = getCacheFile(url);
652 | if (cacheFile != null && cacheFile.exists()) {
653 | cacheFile.delete();
654 | }
655 | }
656 |
657 | private static final int PLAY_PREPARE = 0x1;
658 | private static final int PLAY_LOADING = 0x2;
659 | private static final int PLAY_COMPLETE = 0x3;
660 | private static final int PLAY_FALIED = 0x4;
661 |
662 | public Handler mHandler = new SocketProxyHandler(this);
663 |
664 | /*
665 | * handler
666 | */
667 | class SocketProxyHandler extends Handler {
668 |
669 | /**
670 | * 持有这个类的弱引用,这样能避免发生内存泄露
671 | *
672 | * @param msg
673 | */
674 | WeakReference mH;
675 |
676 | SocketProxyHandler(SocketProxyPlay h) {
677 | mH = new WeakReference(h);
678 | }
679 |
680 | @Override
681 | public void handleMessage(Message msg) {
682 | switch (msg.what) {
683 | case PLAY_PREPARE:
684 | // if(mH.get().mReadMediaListener != null){
685 | // mH.get().mReadMediaListener.onPrepare();
686 | // }
687 |
688 | break;
689 | case PLAY_LOADING:
690 | // if(mH.get().mReadMediaListener != null){
691 | // int[] loadingP = (int[]) msg.obj;
692 | // if(loadingP == null){
693 | // throw new
694 | // NullPointerException("you must write this param loading");
695 | // }
696 | // mH.get().mReadMediaListener.onLoading(loadingP[0],
697 | // loadingP[1]);
698 | // }
699 | break;
700 | case PLAY_FALIED:
701 | // if(mH.get().mReadMediaListener != null){
702 | // String errMsg = "";
703 | // if(msg.obj != null){
704 | // errMsg = (String) msg.obj;
705 | // }
706 | // mH.get().mReadMediaListener.onLoadFailed(errMsg);
707 | // }
708 | break;
709 | case PLAY_COMPLETE:
710 | // if(mH.get().mReadMediaListener != null){
711 | // mH.get().mReadMediaListener.onLoadComplete();
712 | // }
713 | break;
714 | default:
715 | break;
716 | }
717 | }
718 | }
719 |
720 | public void close() {
721 | if (mThisClient != null && !mThisClient.isClosed()) {
722 | try {
723 | mThisClient.close();
724 | } catch (IOException e) {
725 | // L.e( msg);
726 | } finally {
727 | // mThisClient = null;
728 | }
729 | }
730 |
731 | // try {
732 | // if (AspLog.isPrintLog) {
733 | // AspLog.d("close server socket:" + mProxyServerSocket);
734 | // }
735 | // if (mProxyServerSocket != null) {
736 | // mProxyServerSocket.close();
737 | // }
738 | // } catch (IOException e) {
739 | //
740 | // } finally {
741 | // // mProxyServerSocket = null;
742 | // }
743 | mListeningRequest.mSocketNeedClose = true;
744 | mThread.interrupt();
745 | mThread = null;
746 | mListeningRequest = null;
747 |
748 | }
749 |
750 | }
751 |
--------------------------------------------------------------------------------
/player/src/main/java/line/hee/library/StorageUtils.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright 2011-2014 Sergey Tarasevich
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *******************************************************************************/
16 | package line.hee.library;
17 |
18 | import android.app.Activity;
19 | import android.app.Application;
20 | import android.content.Context;
21 | import android.content.ContextWrapper;
22 | import android.content.pm.PackageManager;
23 | import android.os.Build;
24 | import android.os.Environment;
25 |
26 | import java.io.File;
27 | import java.io.IOException;
28 |
29 | import line.hee.library.tools.L;
30 |
31 | import static android.os.Environment.MEDIA_MOUNTED;
32 |
33 | /**
34 | * Provides application storage paths
35 | *
36 | * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
37 | * @since 1.0.0
38 | */
39 | public final class StorageUtils {
40 |
41 | private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
42 | private static final String INDIVIDUAL_DIR_NAME = "uil-images";
43 |
44 | private StorageUtils() {
45 | }
46 |
47 | /**
48 | * Returns application cache directory. Cache directory will be created on SD card
49 | * ("/Android/data/[app_package_name]/cache") if card is mounted and app has appropriate permission. Else -
50 | * Android defines cache directory on device's file system.
51 | *
52 | * @param context Application context
53 | * @return Cache {@link File directory}.
54 | * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
55 | * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
56 | */
57 | public static File getCacheDirectory(Context context) {
58 | return getCacheDirectory(context, true);
59 | }
60 |
61 | /**
62 | * Returns application cache directory. Cache directory will be created on SD card
63 | * ("/Android/data/[app_package_name]/cache") (if card is mounted and app has appropriate permission) or
64 | * on device's file system depending incoming parameters.
65 | *
66 | * @param context Application context
67 | * @param preferExternal Whether prefer external location for cache
68 | * @return Cache {@link File directory}.
69 | * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
70 | * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
71 | */
72 | public static File getCacheDirectory(Context context, boolean preferExternal) {
73 | File appCacheDir = null;
74 | String externalStorageState;
75 | try {
76 | externalStorageState = Environment.getExternalStorageState();
77 | } catch (NullPointerException e) { // (sh)it happens (Issue #660)
78 | externalStorageState = "";
79 | } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989)
80 | externalStorageState = "";
81 | }
82 | if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
83 | appCacheDir = getExternalCacheDir(context);
84 | }
85 | if (appCacheDir == null) {
86 | appCacheDir = context.getCacheDir();
87 | }
88 | if (appCacheDir == null) {
89 | String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
90 | L.w("Can't define system cache directory! '%s' will be used.", cacheDirPath);
91 | appCacheDir = new File(cacheDirPath);
92 | }
93 | return appCacheDir;
94 | }
95 |
96 |
97 |
98 | /**
99 | * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
100 | * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has
101 | * appropriate permission. Else - Android defines cache directory on device's file system.
102 | *
103 | * @param context Application context
104 | * @return Cache {@link File directory}
105 | */
106 | public static File getIndividualCacheDirectory(Context context) {
107 | return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME);
108 | }
109 |
110 | /**
111 | * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
112 | * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has
113 | * appropriate permission. Else - Android defines cache directory on device's file system.
114 | *
115 | * @param context Application context
116 | * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
117 | * @return Cache {@link File directory}
118 | */
119 | public static File getIndividualCacheDirectory(Context context, String cacheDir) {
120 | File appCacheDir = getCacheDirectory(context);
121 | File individualCacheDir = new File(appCacheDir, cacheDir);
122 | if (!individualCacheDir.exists()) {
123 | if (!individualCacheDir.mkdir()) {
124 | individualCacheDir = appCacheDir;
125 | }
126 | }
127 | return individualCacheDir;
128 | }
129 |
130 |
131 |
132 | /**
133 | * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card
134 | * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system.
135 | *
136 | * @param context Application context
137 | * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
138 | * @return Cache {@link File directory}
139 | */
140 | public static File getOwnCacheDirectory(Context context, String cacheDir) {
141 | File appCacheDir = null;
142 | if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) {
143 | appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
144 | }
145 | if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {
146 | appCacheDir = context.getCacheDir();
147 | }
148 | return appCacheDir;
149 | }
150 |
151 | /**
152 | * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card
153 | * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system.
154 | *
155 | * @param context Application context
156 | * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
157 | * @return Cache {@link File directory}
158 | */
159 | public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal) {
160 | File appCacheDir = null;
161 | if (preferExternal && MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) {
162 | appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir);
163 | }
164 | if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) {
165 | appCacheDir = context.getCacheDir();
166 | }
167 | return appCacheDir;
168 | }
169 |
170 | private static File getExternalCacheDir(Context context) {
171 | File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
172 | File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
173 | if (!appCacheDir.exists()) {
174 | if (!appCacheDir.mkdirs()) {
175 | L.w("Unable to create external cache directory");
176 | return null;
177 | }
178 | try {
179 | new File(appCacheDir, ".nomedia").createNewFile();
180 | } catch (IOException e) {
181 | L.i("Can't create \".nomedia\" file in application external cache directory");
182 | }
183 | }
184 | return appCacheDir;
185 | }
186 |
187 | private static boolean hasExternalStoragePermission(Context context) {
188 | int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
189 | return perm == PackageManager.PERMISSION_GRANTED;
190 | }
191 |
192 |
193 |
194 |
195 | }
196 |
--------------------------------------------------------------------------------
/player/src/main/java/line/hee/library/tools/L.java:
--------------------------------------------------------------------------------
1 | package line.hee.library.tools;
2 |
3 | import android.util.Log;
4 |
5 |
6 | public final class L {
7 |
8 | private static final String LOG_FORMAT = "%1$s\n%2$s";
9 | private static volatile boolean writeDebugLogs = true;
10 | private static volatile boolean writeLogs = true;
11 |
12 | private static String TAG = "ProxyPlayer";
13 |
14 | private L() {
15 | }
16 |
17 | @Deprecated
18 | public static void enableLogging() {
19 | writeLogs(true);
20 | }
21 |
22 | @Deprecated
23 | public static void disableLogging() {
24 | writeLogs(false);
25 | }
26 |
27 |
28 | public static void writeDebugLogs(boolean writeDebugLogs) {
29 | L.writeDebugLogs = writeDebugLogs;
30 | }
31 |
32 | public static void writeLogs(boolean writeLogs) {
33 | L.writeLogs = writeLogs;
34 | }
35 |
36 | public static void d(String message, Object... args) {
37 | if (writeDebugLogs) {
38 | log(Log.DEBUG, null, message, args);
39 | }
40 | }
41 |
42 | public static void i(String message, Object... args) {
43 | log(Log.INFO, null, message, args);
44 | }
45 |
46 | public static void w(String message, Object... args) {
47 | log(Log.WARN, null, message, args);
48 | }
49 |
50 | public static void e(Throwable ex) {
51 | log(Log.ERROR, ex, null);
52 | }
53 |
54 | public static void e(String message, Object... args) {
55 | log(Log.ERROR, null, message, args);
56 | }
57 |
58 | public static void e(Throwable ex, String message, Object... args) {
59 | log(Log.ERROR, ex, message, args);
60 | }
61 |
62 | private static void log(int priority, Throwable ex, String message, Object... args) {
63 | if (!writeLogs) return;
64 | if (args.length > 0) {
65 | message = String.format(message, args);
66 | }
67 |
68 | String log;
69 | if (ex == null) {
70 | log = message;
71 | } else {
72 | String logMessage = message == null ? ex.getMessage() : message;
73 | String logBody = Log.getStackTraceString(ex);
74 | log = String.format(LOG_FORMAT, logMessage, logBody);
75 | }
76 | Log.println(priority, TAG, log);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/player/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 |
--------------------------------------------------------------------------------
/player/src/test/java/line/hee/library/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package line.hee.library;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':player'
2 |
--------------------------------------------------------------------------------