├── .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 | --------------------------------------------------------------------------------