Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
NotesChapter
GSYVideoPlayer
提交
a9cd34f4
G
GSYVideoPlayer
项目概览
NotesChapter
/
GSYVideoPlayer
与 Fork 源项目一致
从无法访问的项目Fork
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
GSYVideoPlayer
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
a9cd34f4
编写于
12月 03, 2016
作者:
S
shuyu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.3.8 将videoCache提交为版本
上级
22eaaaa7
变更
60
隐藏空白更改
内联
并排
Showing
60 changed file
with
10 addition
and
2467 deletion
+10
-2467
README.md
README.md
+4
-3
gradle.properties
gradle.properties
+3
-3
gsyVideoPlayer/build.gradle
gsyVideoPlayer/build.gradle
+2
-1
settings.gradle
settings.gradle
+1
-1
videoCache/.gitignore
videoCache/.gitignore
+0
-10
videoCache/build.gradle
videoCache/build.gradle
+0
-49
videoCache/build/generated/source/buildConfig/androidTest/debug/com/danikula/videocache/test/BuildConfig.java
...dTest/debug/com/danikula/videocache/test/BuildConfig.java
+0
-13
videoCache/build/generated/source/buildConfig/debug/com/danikula/videocache/BuildConfig.java
...uildConfig/debug/com/danikula/videocache/BuildConfig.java
+0
-13
videoCache/build/intermediates/bundles/debug/AndroidManifest.xml
...che/build/intermediates/bundles/debug/AndroidManifest.xml
+0
-14
videoCache/build/intermediates/bundles/release/AndroidManifest.xml
...e/build/intermediates/bundles/release/AndroidManifest.xml
+0
-14
videoCache/build/intermediates/incremental/compileDebugAidl/dependency.store
...termediates/incremental/compileDebugAidl/dependency.store
+0
-0
videoCache/build/intermediates/incremental/compileDebugAndroidTestAidl/dependency.store
.../incremental/compileDebugAndroidTestAidl/dependency.store
+0
-0
videoCache/build/intermediates/incremental/compileReleaseAidl/dependency.store
...rmediates/incremental/compileReleaseAidl/dependency.store
+0
-0
videoCache/build/intermediates/incremental/mergeDebugAndroidTestResources/compile-file-map.properties
...ergeDebugAndroidTestResources/compile-file-map.properties
+0
-1
videoCache/build/intermediates/incremental/mergeDebugAndroidTestResources/merger.xml
...tes/incremental/mergeDebugAndroidTestResources/merger.xml
+0
-2
videoCache/build/intermediates/incremental/mergeDebugAssets/merger.xml
...ild/intermediates/incremental/mergeDebugAssets/merger.xml
+0
-2
videoCache/build/intermediates/incremental/mergeDebugShaders/merger.xml
...ld/intermediates/incremental/mergeDebugShaders/merger.xml
+0
-2
videoCache/build/intermediates/incremental/mergeReleaseAssets/merger.xml
...d/intermediates/incremental/mergeReleaseAssets/merger.xml
+0
-2
videoCache/build/intermediates/incremental/mergeReleaseShaders/merger.xml
.../intermediates/incremental/mergeReleaseShaders/merger.xml
+0
-2
videoCache/build/intermediates/incremental/packageDebugResources/compile-file-map.properties
...emental/packageDebugResources/compile-file-map.properties
+0
-1
videoCache/build/intermediates/incremental/packageDebugResources/merger.xml
...ntermediates/incremental/packageDebugResources/merger.xml
+0
-2
videoCache/build/intermediates/incremental/packageReleaseResources/compile-file-map.properties
...ental/packageReleaseResources/compile-file-map.properties
+0
-1
videoCache/build/intermediates/incremental/packageReleaseResources/merger.xml
...ermediates/incremental/packageReleaseResources/merger.xml
+0
-2
videoCache/build/intermediates/manifest/androidTest/debug/AndroidManifest.xml
...ermediates/manifest/androidTest/debug/AndroidManifest.xml
+0
-20
videoCache/build/intermediates/manifests/aapt/debug/AndroidManifest.xml
...ld/intermediates/manifests/aapt/debug/AndroidManifest.xml
+0
-14
videoCache/build/intermediates/manifests/aapt/release/AndroidManifest.xml
.../intermediates/manifests/aapt/release/AndroidManifest.xml
+0
-14
videoCache/build/intermediates/res/resources-debug-androidTest.ap_
...e/build/intermediates/res/resources-debug-androidTest.ap_
+0
-0
videoCache/src/main/AndroidManifest.xml
videoCache/src/main/AndroidManifest.xml
+0
-5
videoCache/src/main/java/com/danikula/videocache/ByteArrayCache.java
...src/main/java/com/danikula/videocache/ByteArrayCache.java
+0
-63
videoCache/src/main/java/com/danikula/videocache/ByteArraySource.java
...rc/main/java/com/danikula/videocache/ByteArraySource.java
+0
-39
videoCache/src/main/java/com/danikula/videocache/Cache.java
videoCache/src/main/java/com/danikula/videocache/Cache.java
+0
-21
videoCache/src/main/java/com/danikula/videocache/CacheListener.java
.../src/main/java/com/danikula/videocache/CacheListener.java
+0
-14
videoCache/src/main/java/com/danikula/videocache/Config.java
videoCache/src/main/java/com/danikula/videocache/Config.java
+0
-33
videoCache/src/main/java/com/danikula/videocache/GetRequest.java
...che/src/main/java/com/danikula/videocache/GetRequest.java
+0
-71
videoCache/src/main/java/com/danikula/videocache/HttpProxyCache.java
...src/main/java/com/danikula/videocache/HttpProxyCache.java
+0
-107
videoCache/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java
...in/java/com/danikula/videocache/HttpProxyCacheServer.java
+0
-432
videoCache/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java
.../com/danikula/videocache/HttpProxyCacheServerClients.java
+0
-115
videoCache/src/main/java/com/danikula/videocache/HttpUrlSource.java
.../src/main/java/com/danikula/videocache/HttpUrlSource.java
+0
-179
videoCache/src/main/java/com/danikula/videocache/InterruptedProxyCacheException.java
...m/danikula/videocache/InterruptedProxyCacheException.java
+0
-21
videoCache/src/main/java/com/danikula/videocache/Pinger.java
videoCache/src/main/java/com/danikula/videocache/Pinger.java
+0
-112
videoCache/src/main/java/com/danikula/videocache/Preconditions.java
.../src/main/java/com/danikula/videocache/Preconditions.java
+0
-38
videoCache/src/main/java/com/danikula/videocache/ProxyCache.java
...che/src/main/java/com/danikula/videocache/ProxyCache.java
+0
-190
videoCache/src/main/java/com/danikula/videocache/ProxyCacheException.java
...ain/java/com/danikula/videocache/ProxyCacheException.java
+0
-21
videoCache/src/main/java/com/danikula/videocache/ProxyCacheUtils.java
...rc/main/java/com/danikula/videocache/ProxyCacheUtils.java
+0
-97
videoCache/src/main/java/com/danikula/videocache/Source.java
videoCache/src/main/java/com/danikula/videocache/Source.java
+0
-40
videoCache/src/main/java/com/danikula/videocache/SourceInfo.java
...che/src/main/java/com/danikula/videocache/SourceInfo.java
+0
-28
videoCache/src/main/java/com/danikula/videocache/StorageUtils.java
...e/src/main/java/com/danikula/videocache/StorageUtils.java
+0
-83
videoCache/src/main/java/com/danikula/videocache/file/DiskUsage.java
...src/main/java/com/danikula/videocache/file/DiskUsage.java
+0
-15
videoCache/src/main/java/com/danikula/videocache/file/FileCache.java
...src/main/java/com/danikula/videocache/file/FileCache.java
+0
-126
videoCache/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
.../java/com/danikula/videocache/file/FileNameGenerator.java
+0
-12
videoCache/src/main/java/com/danikula/videocache/file/Files.java
...che/src/main/java/com/danikula/videocache/file/Files.java
+0
-88
videoCache/src/main/java/com/danikula/videocache/file/LruDiskUsage.java
.../main/java/com/danikula/videocache/file/LruDiskUsage.java
+0
-77
videoCache/src/main/java/com/danikula/videocache/file/Md5FileNameGenerator.java
...va/com/danikula/videocache/file/Md5FileNameGenerator.java
+0
-29
videoCache/src/main/java/com/danikula/videocache/file/TotalCountLruDiskUsage.java
.../com/danikula/videocache/file/TotalCountLruDiskUsage.java
+0
-25
videoCache/src/main/java/com/danikula/videocache/file/TotalSizeLruDiskUsage.java
...a/com/danikula/videocache/file/TotalSizeLruDiskUsage.java
+0
-25
videoCache/src/main/java/com/danikula/videocache/file/UnlimitedDiskUsage.java
...java/com/danikula/videocache/file/UnlimitedDiskUsage.java
+0
-17
videoCache/src/main/java/com/danikula/videocache/sourcestorage/DatabaseSourceInfoStorage.java
...a/videocache/sourcestorage/DatabaseSourceInfoStorage.java
+0
-98
videoCache/src/main/java/com/danikula/videocache/sourcestorage/NoSourceInfoStorage.java
...anikula/videocache/sourcestorage/NoSourceInfoStorage.java
+0
-24
videoCache/src/main/java/com/danikula/videocache/sourcestorage/SourceInfoStorage.java
.../danikula/videocache/sourcestorage/SourceInfoStorage.java
+0
-17
videoCache/src/main/java/com/danikula/videocache/sourcestorage/SourceInfoStorageFactory.java
...la/videocache/sourcestorage/SourceInfoStorageFactory.java
+0
-19
未找到文件。
README.md
浏览文件 @
a9cd34f4
...
...
@@ -20,14 +20,14 @@
<dependency>
<groupId>com.shuyu</groupId>
<artifactId>gsyVideoPlayer</artifactId>
<version>1.3.
7
</version>
<version>1.3.
8
</version>
<type>pom</type>
</dependency>
```
```
dependencies {
compile 'com.shuyu:gsyVideoPlayer:1.3.
7
'
compile 'com.shuyu:gsyVideoPlayer:1.3.
8
'
}
```
...
...
@@ -55,7 +55,8 @@ dependencies {
### 1.3.8
*
添加了lib封面对复用封面的支持和demo。
*
修复了缓冲进度条;增加了recyclerView。
*
修复了缓冲进度条;增加了recyclerViewDemo。
*
直接导入videoCache,去除输出log。
*
修正了列表中隐藏虚拟键盘与actionbar的冲突。
...
...
gradle.properties
浏览文件 @
a9cd34f4
...
...
@@ -15,10 +15,10 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
BINTRAY_USER
=
BINTRAY_KEY
=
BINTRAY_USER
=
plane18
BINTRAY_KEY
=
b3bbd913cbf1350cbda1cad77410746d2a67372c
PROJ_GROUP
=
com.shuyu
PROJ_VERSION
=
1.3.
7
PROJ_VERSION
=
1.3.
9
PROJ_NAME
=
gsyVideo
PROJ_WEBSITEURL
=
hhttps://github.com/CarGuo/GSYVideoPlayer
PROJ_ISSUETRACKERURL
=
...
...
gsyVideoPlayer/build.gradle
浏览文件 @
a9cd34f4
...
...
@@ -25,14 +25,15 @@ android {
dependencies
{
compile
fileTree
(
dir:
'libs'
,
include:
[
'*.jar'
])
compile
project
(
':videoCache'
)
def
viewDependencies
=
rootProject
.
ext
.
viewDependencies
def
dataDependencies
=
rootProject
.
ext
.
dataDependencies
compile
viewDependencies
.
ijkplayer_java
compile
viewDependencies
.
ijkplayer_armv7a
compile
viewDependencies
.
ijkplayer_armv5
compile
viewDependencies
.
ijkplayer_x86
compile
viewDependencies
.
ijkplayer_exo
compile
viewDependencies
.
transitionseverywhere
compile
dataDependencies
.
videocache
compile
androidDependencies
.
support_v4
compile
androidDependencies
.
appcompat_v7
}
...
...
settings.gradle
浏览文件 @
a9cd34f4
include
':app'
,
':gsyVideoPlayer'
,
':videoCache'
include
':app'
,
':gsyVideoPlayer'
videoCache/.gitignore
已删除
100644 → 0
浏览文件 @
22eaaaa7
*.iml
.gradle
/local.properties
.DS_Store
/build
/.idea
/captures
.externalNativeBuild
*.apk
keyid
\ No newline at end of file
videoCache/build.gradle
已删除
100644 → 0
浏览文件 @
22eaaaa7
buildscript
{
repositories
{
jcenter
()
}
dependencies
{
classpath
'com.novoda:bintray-release:0.3.4'
}
}
apply
plugin:
'com.android.library'
apply
plugin:
'idea'
apply
plugin:
'bintray-release'
android
{
compileSdkVersion
23
buildToolsVersion
'22.0.1'
defaultConfig
{
minSdkVersion
9
targetSdkVersion
23
versionCode
19
versionName
'2.6.3'
}
compileOptions
{
sourceCompatibility
JavaVersion
.
VERSION_1_7
targetCompatibility
JavaVersion
.
VERSION_1_7
}
}
idea
{
module
{
downloadJavadoc
=
true
downloadSources
=
true
}
}
dependencies
{
compile
'org.slf4j:slf4j-android:1.7.21'
}
publish
{
userOrg
=
'alexeydanilov'
groupId
=
'com.danikula'
artifactId
=
'videocache'
publishVersion
=
'2.6.3'
description
=
'Cache support for android VideoView'
website
=
'https://github.com/danikula/AndroidVideoCache'
}
videoCache/build/generated/source/buildConfig/androidTest/debug/com/danikula/videocache/test/BuildConfig.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
/**
* Automatically generated file. DO NOT MODIFY
*/
package
com.danikula.videocache.test
;
public
final
class
BuildConfig
{
public
static
final
boolean
DEBUG
=
Boolean
.
parseBoolean
(
"true"
);
public
static
final
String
APPLICATION_ID
=
"com.danikula.videocache.test"
;
public
static
final
String
BUILD_TYPE
=
"debug"
;
public
static
final
String
FLAVOR
=
""
;
public
static
final
int
VERSION_CODE
=
19
;
public
static
final
String
VERSION_NAME
=
"2.6.3"
;
}
videoCache/build/generated/source/buildConfig/debug/com/danikula/videocache/BuildConfig.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
/**
* Automatically generated file. DO NOT MODIFY
*/
package
com.danikula.videocache
;
public
final
class
BuildConfig
{
public
static
final
boolean
DEBUG
=
Boolean
.
parseBoolean
(
"true"
);
public
static
final
String
APPLICATION_ID
=
"com.danikula.videocache"
;
public
static
final
String
BUILD_TYPE
=
"debug"
;
public
static
final
String
FLAVOR
=
""
;
public
static
final
int
VERSION_CODE
=
19
;
public
static
final
String
VERSION_NAME
=
"2.6.3"
;
}
videoCache/build/intermediates/bundles/debug/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"com.danikula.videocache"
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:versionCode=
"19"
android:versionName=
"2.6.3"
>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"23"
/>
<application
/>
</manifest>
\ No newline at end of file
videoCache/build/intermediates/bundles/release/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"com.danikula.videocache"
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:versionCode=
"19"
android:versionName=
"2.6.3"
>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"23"
/>
<application
/>
</manifest>
\ No newline at end of file
videoCache/build/intermediates/incremental/compileDebugAidl/dependency.store
已删除
100644 → 0
浏览文件 @
22eaaaa7
文件已删除
videoCache/build/intermediates/incremental/compileDebugAndroidTestAidl/dependency.store
已删除
100644 → 0
浏览文件 @
22eaaaa7
文件已删除
videoCache/build/intermediates/incremental/compileReleaseAidl/dependency.store
已删除
100644 → 0
浏览文件 @
22eaaaa7
文件已删除
videoCache/build/intermediates/incremental/mergeDebugAndroidTestResources/compile-file-map.properties
已删除
100644 → 0
浏览文件 @
22eaaaa7
#Sat Dec 03 14:06:59 GMT+08:00 2016
videoCache/build/intermediates/incremental/mergeDebugAndroidTestResources/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"debug$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\intermediates\bundles\debug\res"
/></dataSet><dataSet
config=
"main$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\androidTest\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\androidTest\debug"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\androidTest\debug"
/></dataSet><dataSet
config=
"debug"
from-dependency=
"true"
generated-set=
"debug$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\intermediates\bundles\debug\res"
/></dataSet><dataSet
config=
"main"
generated-set=
"main$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\androidTest\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\androidTest\debug"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\androidTest\debug"
/></dataSet><mergedItems/></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/mergeDebugAssets/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\assets"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\assets\shaders\debug"
/></dataSet><dataSet
config=
"debug"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\debug\assets"
/></dataSet></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/mergeDebugShaders/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\shaders"
/></dataSet><dataSet
config=
"debug"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\debug\shaders"
/></dataSet></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/mergeReleaseAssets/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\assets"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\assets\shaders\release"
/></dataSet><dataSet
config=
"release"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\release\assets"
/></dataSet></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/mergeReleaseShaders/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\shaders"
/></dataSet><dataSet
config=
"release"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\release\shaders"
/></dataSet></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/packageDebugResources/compile-file-map.properties
已删除
100644 → 0
浏览文件 @
22eaaaa7
#Sat Dec 03 14:06:58 GMT+08:00 2016
videoCache/build/intermediates/incremental/packageDebugResources/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\debug"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\debug"
/></dataSet><dataSet
config=
"debug$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\debug\res"
/></dataSet><dataSet
config=
"main"
generated-set=
"main$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\debug"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\debug"
/></dataSet><dataSet
config=
"debug"
generated-set=
"debug$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\debug\res"
/></dataSet><mergedItems/></merger>
\ No newline at end of file
videoCache/build/intermediates/incremental/packageReleaseResources/compile-file-map.properties
已删除
100644 → 0
浏览文件 @
22eaaaa7
#Sat Dec 03 14:06:48 GMT+08:00 2016
videoCache/build/intermediates/incremental/packageReleaseResources/merger.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<merger
version=
"3"
><dataSet
config=
"main$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\release"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\release"
/></dataSet><dataSet
config=
"release$Generated"
generated=
"true"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\release\res"
/></dataSet><dataSet
config=
"main"
generated-set=
"main$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\main\res"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\rs\release"
/><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\build\generated\res\resValues\release"
/></dataSet><dataSet
config=
"release"
generated-set=
"release$Generated"
><source
path=
"D:\workSpace\android\Github-Code\GSYVideoPlayer\videoCache\src\release\res"
/></dataSet><mergedItems/></merger>
\ No newline at end of file
videoCache/build/intermediates/manifest/androidTest/debug/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.danikula.videocache.test"
>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"23"
/>
<instrumentation
android:name=
"android.test.InstrumentationTestRunner"
android:functionalTest=
"false"
android:handleProfiling=
"false"
android:label=
"Tests for com.danikula.videocache.test"
android:targetPackage=
"com.danikula.videocache.test"
/>
<application>
<uses-library
android:name=
"android.test.runner"
/>
</application>
</manifest>
\ No newline at end of file
videoCache/build/intermediates/manifests/aapt/debug/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"com.danikula.videocache"
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:versionCode=
"19"
android:versionName=
"2.6.3"
>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"23"
/>
<application
/>
</manifest>
\ No newline at end of file
videoCache/build/intermediates/manifests/aapt/release/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"com.danikula.videocache"
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:versionCode=
"19"
android:versionName=
"2.6.3"
>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"23"
/>
<application
/>
</manifest>
\ No newline at end of file
videoCache/build/intermediates/res/resources-debug-androidTest.ap_
已删除
100644 → 0
浏览文件 @
22eaaaa7
文件已删除
videoCache/src/main/AndroidManifest.xml
已删除
100644 → 0
浏览文件 @
22eaaaa7
<?xml version="1.0" encoding="utf-8"?>
<manifest
package=
"com.danikula.videocache"
>
<application
/>
</manifest>
videoCache/src/main/java/com/danikula/videocache/ByteArrayCache.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
java.io.ByteArrayInputStream
;
import
java.util.Arrays
;
/**
* Simple memory based {@link Cache} implementation.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
ByteArrayCache
implements
Cache
{
private
volatile
byte
[]
data
;
private
volatile
boolean
completed
;
public
ByteArrayCache
()
{
this
(
new
byte
[
0
]);
}
public
ByteArrayCache
(
byte
[]
data
)
{
this
.
data
=
Preconditions
.
checkNotNull
(
data
);
}
@Override
public
int
read
(
byte
[]
buffer
,
long
offset
,
int
length
)
throws
ProxyCacheException
{
if
(
offset
>=
data
.
length
)
{
return
-
1
;
}
if
(
offset
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalArgumentException
(
"Too long offset for memory cache "
+
offset
);
}
return
new
ByteArrayInputStream
(
data
).
read
(
buffer
,
(
int
)
offset
,
length
);
}
@Override
public
int
available
()
throws
ProxyCacheException
{
return
data
.
length
;
}
@Override
public
void
append
(
byte
[]
newData
,
int
length
)
throws
ProxyCacheException
{
Preconditions
.
checkNotNull
(
data
);
Preconditions
.
checkArgument
(
length
>=
0
&&
length
<=
newData
.
length
);
byte
[]
appendedData
=
Arrays
.
copyOf
(
data
,
data
.
length
+
length
);
System
.
arraycopy
(
newData
,
0
,
appendedData
,
data
.
length
,
length
);
data
=
appendedData
;
}
@Override
public
void
close
()
throws
ProxyCacheException
{
}
@Override
public
void
complete
()
{
completed
=
true
;
}
@Override
public
boolean
isCompleted
()
{
return
completed
;
}
}
videoCache/src/main/java/com/danikula/videocache/ByteArraySource.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
java.io.ByteArrayInputStream
;
/**
* Simple memory based {@link Source} implementation.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
ByteArraySource
implements
Source
{
private
final
byte
[]
data
;
private
ByteArrayInputStream
arrayInputStream
;
public
ByteArraySource
(
byte
[]
data
)
{
this
.
data
=
data
;
}
@Override
public
int
read
(
byte
[]
buffer
)
throws
ProxyCacheException
{
return
arrayInputStream
.
read
(
buffer
,
0
,
buffer
.
length
);
}
@Override
public
int
length
()
throws
ProxyCacheException
{
return
data
.
length
;
}
@Override
public
void
open
(
int
offset
)
throws
ProxyCacheException
{
arrayInputStream
=
new
ByteArrayInputStream
(
data
);
arrayInputStream
.
skip
(
offset
);
}
@Override
public
void
close
()
throws
ProxyCacheException
{
}
}
videoCache/src/main/java/com/danikula/videocache/Cache.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
/**
* Cache for proxy.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
Cache
{
int
available
()
throws
ProxyCacheException
;
int
read
(
byte
[]
buffer
,
long
offset
,
int
length
)
throws
ProxyCacheException
;
void
append
(
byte
[]
data
,
int
length
)
throws
ProxyCacheException
;
void
close
()
throws
ProxyCacheException
;
void
complete
()
throws
ProxyCacheException
;
boolean
isCompleted
();
}
videoCache/src/main/java/com/danikula/videocache/CacheListener.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
java.io.File
;
/**
* Listener for cache availability.
*
* @author Egor Makovsky (yahor.makouski@gmail.com)
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
CacheListener
{
void
onCacheAvailable
(
File
cacheFile
,
String
url
,
int
percentsAvailable
);
}
videoCache/src/main/java/com/danikula/videocache/Config.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
com.danikula.videocache.file.DiskUsage
;
import
com.danikula.videocache.file.FileNameGenerator
;
import
com.danikula.videocache.sourcestorage.SourceInfoStorage
;
import
java.io.File
;
/**
* Configuration for proxy cache.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
Config
{
public
final
File
cacheRoot
;
public
final
FileNameGenerator
fileNameGenerator
;
public
final
DiskUsage
diskUsage
;
public
final
SourceInfoStorage
sourceInfoStorage
;
Config
(
File
cacheRoot
,
FileNameGenerator
fileNameGenerator
,
DiskUsage
diskUsage
,
SourceInfoStorage
sourceInfoStorage
)
{
this
.
cacheRoot
=
cacheRoot
;
this
.
fileNameGenerator
=
fileNameGenerator
;
this
.
diskUsage
=
diskUsage
;
this
.
sourceInfoStorage
=
sourceInfoStorage
;
}
File
generateCacheFile
(
String
url
)
{
String
name
=
fileNameGenerator
.
generate
(
url
);
return
new
File
(
cacheRoot
,
name
);
}
}
videoCache/src/main/java/com/danikula/videocache/GetRequest.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.text.TextUtils
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Model for Http GET request.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
GetRequest
{
private
static
final
Pattern
RANGE_HEADER_PATTERN
=
Pattern
.
compile
(
"[R,r]ange:[ ]?bytes=(\\d*)-"
);
private
static
final
Pattern
URL_PATTERN
=
Pattern
.
compile
(
"GET /(.*) HTTP"
);
public
final
String
uri
;
public
final
long
rangeOffset
;
public
final
boolean
partial
;
public
GetRequest
(
String
request
)
{
checkNotNull
(
request
);
long
offset
=
findRangeOffset
(
request
);
this
.
rangeOffset
=
Math
.
max
(
0
,
offset
);
this
.
partial
=
offset
>=
0
;
this
.
uri
=
findUri
(
request
);
}
public
static
GetRequest
read
(
InputStream
inputStream
)
throws
IOException
{
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
inputStream
,
"UTF-8"
));
StringBuilder
stringRequest
=
new
StringBuilder
();
String
line
;
while
(!
TextUtils
.
isEmpty
(
line
=
reader
.
readLine
()))
{
// until new line (headers ending)
stringRequest
.
append
(
line
).
append
(
'\n'
);
}
return
new
GetRequest
(
stringRequest
.
toString
());
}
private
long
findRangeOffset
(
String
request
)
{
Matcher
matcher
=
RANGE_HEADER_PATTERN
.
matcher
(
request
);
if
(
matcher
.
find
())
{
String
rangeValue
=
matcher
.
group
(
1
);
return
Long
.
parseLong
(
rangeValue
);
}
return
-
1
;
}
private
String
findUri
(
String
request
)
{
Matcher
matcher
=
URL_PATTERN
.
matcher
(
request
);
if
(
matcher
.
find
())
{
return
matcher
.
group
(
1
);
}
throw
new
IllegalArgumentException
(
"Invalid request `"
+
request
+
"`: url not found!"
);
}
@Override
public
String
toString
()
{
return
"GetRequest{"
+
"rangeOffset="
+
rangeOffset
+
", partial="
+
partial
+
", uri='"
+
uri
+
'\''
+
'}'
;
}
}
videoCache/src/main/java/com/danikula/videocache/HttpProxyCache.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.text.TextUtils
;
import
com.danikula.videocache.file.FileCache
;
import
java.io.BufferedOutputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.net.Socket
;
import
static
com
.
danikula
.
videocache
.
ProxyCacheUtils
.
DEFAULT_BUFFER_SIZE
;
/**
* {@link ProxyCache} that read http url and writes data to {@link Socket}
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
HttpProxyCache
extends
ProxyCache
{
private
static
final
float
NO_CACHE_BARRIER
=
.
2
f
;
private
final
HttpUrlSource
source
;
private
final
FileCache
cache
;
private
CacheListener
listener
;
public
HttpProxyCache
(
HttpUrlSource
source
,
FileCache
cache
)
{
super
(
source
,
cache
);
this
.
cache
=
cache
;
this
.
source
=
source
;
}
public
void
registerCacheListener
(
CacheListener
cacheListener
)
{
this
.
listener
=
cacheListener
;
}
public
void
processRequest
(
GetRequest
request
,
Socket
socket
)
throws
IOException
,
ProxyCacheException
{
OutputStream
out
=
new
BufferedOutputStream
(
socket
.
getOutputStream
());
String
responseHeaders
=
newResponseHeaders
(
request
);
out
.
write
(
responseHeaders
.
getBytes
(
"UTF-8"
));
long
offset
=
request
.
rangeOffset
;
if
(
isUseCache
(
request
))
{
responseWithCache
(
out
,
offset
);
}
else
{
responseWithoutCache
(
out
,
offset
);
}
}
private
boolean
isUseCache
(
GetRequest
request
)
throws
ProxyCacheException
{
int
sourceLength
=
source
.
length
();
boolean
sourceLengthKnown
=
sourceLength
>
0
;
int
cacheAvailable
=
cache
.
available
();
// do not use cache for partial requests which too far from available cache. It seems user seek video.
return
!
sourceLengthKnown
||
!
request
.
partial
||
request
.
rangeOffset
<=
cacheAvailable
+
sourceLength
*
NO_CACHE_BARRIER
;
}
private
String
newResponseHeaders
(
GetRequest
request
)
throws
IOException
,
ProxyCacheException
{
String
mime
=
source
.
getMime
();
boolean
mimeKnown
=
!
TextUtils
.
isEmpty
(
mime
);
int
length
=
cache
.
isCompleted
()
?
cache
.
available
()
:
source
.
length
();
boolean
lengthKnown
=
length
>=
0
;
long
contentLength
=
request
.
partial
?
length
-
request
.
rangeOffset
:
length
;
boolean
addRange
=
lengthKnown
&&
request
.
partial
;
return
new
StringBuilder
()
.
append
(
request
.
partial
?
"HTTP/1.1 206 PARTIAL CONTENT\n"
:
"HTTP/1.1 200 OK\n"
)
.
append
(
"Accept-Ranges: bytes\n"
)
.
append
(
lengthKnown
?
String
.
format
(
"Content-Length: %d\n"
,
contentLength
)
:
""
)
.
append
(
addRange
?
String
.
format
(
"Content-Range: bytes %d-%d/%d\n"
,
request
.
rangeOffset
,
length
-
1
,
length
)
:
""
)
.
append
(
mimeKnown
?
String
.
format
(
"Content-Type: %s\n"
,
mime
)
:
""
)
.
append
(
"\n"
)
// headers end
.
toString
();
}
private
void
responseWithCache
(
OutputStream
out
,
long
offset
)
throws
ProxyCacheException
,
IOException
{
byte
[]
buffer
=
new
byte
[
DEFAULT_BUFFER_SIZE
];
int
readBytes
;
while
((
readBytes
=
read
(
buffer
,
offset
,
buffer
.
length
))
!=
-
1
)
{
out
.
write
(
buffer
,
0
,
readBytes
);
offset
+=
readBytes
;
}
out
.
flush
();
}
private
void
responseWithoutCache
(
OutputStream
out
,
long
offset
)
throws
ProxyCacheException
,
IOException
{
HttpUrlSource
newSourceNoCache
=
new
HttpUrlSource
(
this
.
source
);
try
{
newSourceNoCache
.
open
((
int
)
offset
);
byte
[]
buffer
=
new
byte
[
DEFAULT_BUFFER_SIZE
];
int
readBytes
;
while
((
readBytes
=
newSourceNoCache
.
read
(
buffer
))
!=
-
1
)
{
out
.
write
(
buffer
,
0
,
readBytes
);
offset
+=
readBytes
;
}
out
.
flush
();
}
finally
{
newSourceNoCache
.
close
();
}
}
@Override
protected
void
onCachePercentsAvailableChanged
(
int
percents
)
{
if
(
listener
!=
null
)
{
listener
.
onCacheAvailable
(
cache
.
file
,
source
.
getUrl
(),
percents
);
}
}
}
videoCache/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.content.Context
;
import
android.net.Uri
;
import
com.danikula.videocache.file.DiskUsage
;
import
com.danikula.videocache.file.FileNameGenerator
;
import
com.danikula.videocache.file.Md5FileNameGenerator
;
import
com.danikula.videocache.file.TotalCountLruDiskUsage
;
import
com.danikula.videocache.file.TotalSizeLruDiskUsage
;
import
com.danikula.videocache.sourcestorage.SourceInfoStorage
;
import
com.danikula.videocache.sourcestorage.SourceInfoStorageFactory
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.File
;
import
java.io.IOException
;
import
java.net.InetAddress
;
import
java.net.ServerSocket
;
import
java.net.Socket
;
import
java.net.SocketException
;
import
java.util.Locale
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.CountDownLatch
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkAllNotNull
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Simple lightweight proxy server with file caching support that handles HTTP requests.
* Typical usage:
* <pre><code>
* public onCreate(Bundle state) {
* super.onCreate(state);
* <p/>
* HttpProxyCacheServer proxy = getProxy();
* String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
* videoView.setVideoPath(proxyUrl);
* }
* <p/>
* private HttpProxyCacheServer getProxy() {
* // should return single instance of HttpProxyCacheServer shared for whole app.
* }
* <code/></pre>
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
HttpProxyCacheServer
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"HttpProxyCacheServer"
);
private
static
final
String
PROXY_HOST
=
"127.0.0.1"
;
private
final
Object
clientsLock
=
new
Object
();
private
final
ExecutorService
socketProcessor
=
Executors
.
newFixedThreadPool
(
8
);
private
final
Map
<
String
,
HttpProxyCacheServerClients
>
clientsMap
=
new
ConcurrentHashMap
<>();
private
final
ServerSocket
serverSocket
;
private
final
int
port
;
private
final
Thread
waitConnectionThread
;
private
final
Config
config
;
private
final
Pinger
pinger
;
public
HttpProxyCacheServer
(
Context
context
)
{
this
(
new
Builder
(
context
).
buildConfig
());
}
private
HttpProxyCacheServer
(
Config
config
)
{
this
.
config
=
checkNotNull
(
config
);
try
{
InetAddress
inetAddress
=
InetAddress
.
getByName
(
PROXY_HOST
);
this
.
serverSocket
=
new
ServerSocket
(
0
,
8
,
inetAddress
);
this
.
port
=
serverSocket
.
getLocalPort
();
CountDownLatch
startSignal
=
new
CountDownLatch
(
1
);
this
.
waitConnectionThread
=
new
Thread
(
new
WaitRequestsRunnable
(
startSignal
));
this
.
waitConnectionThread
.
start
();
startSignal
.
await
();
// freeze thread, wait for server starts
this
.
pinger
=
new
Pinger
(
PROXY_HOST
,
port
);
LOG
.
info
(
"Proxy cache server started. Is it alive? "
+
isAlive
());
}
catch
(
IOException
|
InterruptedException
e
)
{
socketProcessor
.
shutdown
();
throw
new
IllegalStateException
(
"Error starting local proxy server"
,
e
);
}
}
/**
* Returns url that wrap original url and should be used for client (MediaPlayer, ExoPlayer, etc).
* <p>
* If file for this url is fully cached (it means method {@link #isCached(String)} returns {@code true})
* then file:// uri to cached file will be returned.
* <p>
* Calling this method has same effect as calling {@link #getProxyUrl(String, boolean)} with 2nd parameter set to {@code true}.
*
* @param url a url to file that should be cached.
* @return a wrapped by proxy url if file is not fully cached or url pointed to cache file otherwise.
*/
public
String
getProxyUrl
(
String
url
)
{
return
getProxyUrl
(
url
,
true
);
}
/**
* Returns url that wrap original url and should be used for client (MediaPlayer, ExoPlayer, etc).
* <p>
* If parameter {@code allowCachedFileUri} is {@code true} and file for this url is fully cached
* (it means method {@link #isCached(String)} returns {@code true}) then file:// uri to cached file will be returned.
*
* @param url a url to file that should be cached.
* @param allowCachedFileUri {@code true} if allow to return file:// uri if url is fully cached
* @return a wrapped by proxy url if file is not fully cached or url pointed to cache file otherwise (if {@code allowCachedFileUri} is {@code true}).
*/
public
String
getProxyUrl
(
String
url
,
boolean
allowCachedFileUri
)
{
if
(
allowCachedFileUri
&&
isCached
(
url
))
{
File
cacheFile
=
getCacheFile
(
url
);
touchFileSafely
(
cacheFile
);
return
Uri
.
fromFile
(
cacheFile
).
toString
();
}
return
isAlive
()
?
appendToProxyUrl
(
url
)
:
url
;
}
public
void
registerCacheListener
(
CacheListener
cacheListener
,
String
url
)
{
checkAllNotNull
(
cacheListener
,
url
);
synchronized
(
clientsLock
)
{
try
{
getClients
(
url
).
registerCacheListener
(
cacheListener
);
}
catch
(
ProxyCacheException
e
)
{
LOG
.
warn
(
"Error registering cache listener"
,
e
);
}
}
}
public
void
unregisterCacheListener
(
CacheListener
cacheListener
,
String
url
)
{
checkAllNotNull
(
cacheListener
,
url
);
synchronized
(
clientsLock
)
{
try
{
getClients
(
url
).
unregisterCacheListener
(
cacheListener
);
}
catch
(
ProxyCacheException
e
)
{
LOG
.
warn
(
"Error registering cache listener"
,
e
);
}
}
}
public
void
unregisterCacheListener
(
CacheListener
cacheListener
)
{
checkNotNull
(
cacheListener
);
synchronized
(
clientsLock
)
{
for
(
HttpProxyCacheServerClients
clients
:
clientsMap
.
values
())
{
clients
.
unregisterCacheListener
(
cacheListener
);
}
}
}
/**
* Checks is cache contains fully cached file for particular url.
*
* @param url an url cache file will be checked for.
* @return {@code true} if cache contains fully cached file for passed in parameters url.
*/
public
boolean
isCached
(
String
url
)
{
checkNotNull
(
url
,
"Url can't be null!"
);
return
getCacheFile
(
url
).
exists
();
}
public
void
shutdown
()
{
LOG
.
info
(
"Shutdown proxy server"
);
shutdownClients
();
config
.
sourceInfoStorage
.
release
();
waitConnectionThread
.
interrupt
();
try
{
if
(!
serverSocket
.
isClosed
())
{
serverSocket
.
close
();
}
}
catch
(
IOException
e
)
{
onError
(
new
ProxyCacheException
(
"Error shutting down proxy server"
,
e
));
}
}
private
boolean
isAlive
()
{
return
pinger
.
ping
(
3
,
70
);
// 70+140+280=max~500ms
}
private
String
appendToProxyUrl
(
String
url
)
{
return
String
.
format
(
Locale
.
US
,
"http://%s:%d/%s"
,
PROXY_HOST
,
port
,
ProxyCacheUtils
.
encode
(
url
));
}
private
File
getCacheFile
(
String
url
)
{
File
cacheDir
=
config
.
cacheRoot
;
String
fileName
=
config
.
fileNameGenerator
.
generate
(
url
);
return
new
File
(
cacheDir
,
fileName
);
}
private
void
touchFileSafely
(
File
cacheFile
)
{
try
{
config
.
diskUsage
.
touch
(
cacheFile
);
}
catch
(
IOException
e
)
{
LOG
.
error
(
"Error touching file "
+
cacheFile
,
e
);
}
}
private
void
shutdownClients
()
{
synchronized
(
clientsLock
)
{
for
(
HttpProxyCacheServerClients
clients
:
clientsMap
.
values
())
{
clients
.
shutdown
();
}
clientsMap
.
clear
();
}
}
private
void
waitForRequest
()
{
try
{
while
(!
Thread
.
currentThread
().
isInterrupted
())
{
Socket
socket
=
serverSocket
.
accept
();
LOG
.
debug
(
"Accept new socket "
+
socket
);
socketProcessor
.
submit
(
new
SocketProcessorRunnable
(
socket
));
}
}
catch
(
IOException
e
)
{
onError
(
new
ProxyCacheException
(
"Error during waiting connection"
,
e
));
}
}
private
void
processSocket
(
Socket
socket
)
{
try
{
GetRequest
request
=
GetRequest
.
read
(
socket
.
getInputStream
());
LOG
.
debug
(
"Request to cache proxy:"
+
request
);
String
url
=
ProxyCacheUtils
.
decode
(
request
.
uri
);
if
(
pinger
.
isPingRequest
(
url
))
{
pinger
.
responseToPing
(
socket
);
}
else
{
HttpProxyCacheServerClients
clients
=
getClients
(
url
);
clients
.
processRequest
(
request
,
socket
);
}
}
catch
(
SocketException
e
)
{
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
// So just to prevent log flooding don't log stacktrace
LOG
.
debug
(
"Closing socket… Socket is closed by client."
);
}
catch
(
ProxyCacheException
|
IOException
e
)
{
onError
(
new
ProxyCacheException
(
"Error processing request"
,
e
));
}
finally
{
releaseSocket
(
socket
);
LOG
.
debug
(
"Opened connections: "
+
getClientsCount
());
}
}
private
HttpProxyCacheServerClients
getClients
(
String
url
)
throws
ProxyCacheException
{
synchronized
(
clientsLock
)
{
HttpProxyCacheServerClients
clients
=
clientsMap
.
get
(
url
);
if
(
clients
==
null
)
{
clients
=
new
HttpProxyCacheServerClients
(
url
,
config
);
clientsMap
.
put
(
url
,
clients
);
}
return
clients
;
}
}
private
int
getClientsCount
()
{
synchronized
(
clientsLock
)
{
int
count
=
0
;
for
(
HttpProxyCacheServerClients
clients
:
clientsMap
.
values
())
{
count
+=
clients
.
getClientsCount
();
}
return
count
;
}
}
private
void
releaseSocket
(
Socket
socket
)
{
closeSocketInput
(
socket
);
closeSocketOutput
(
socket
);
closeSocket
(
socket
);
}
private
void
closeSocketInput
(
Socket
socket
)
{
try
{
if
(!
socket
.
isInputShutdown
())
{
socket
.
shutdownInput
();
}
}
catch
(
SocketException
e
)
{
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
// So just to prevent log flooding don't log stacktrace
LOG
.
debug
(
"Releasing input stream… Socket is closed by client."
);
}
catch
(
IOException
e
)
{
//onError(new ProxyCacheException("Error closing socket input stream", e));
}
}
private
void
closeSocketOutput
(
Socket
socket
)
{
try
{
if
(!
socket
.
isOutputShutdown
())
{
socket
.
shutdownOutput
();
}
}
catch
(
IOException
e
)
{
//onError(new ProxyCacheException("Error closing socket output stream", e));
}
}
private
void
closeSocket
(
Socket
socket
)
{
try
{
if
(!
socket
.
isClosed
())
{
socket
.
close
();
}
}
catch
(
IOException
e
)
{
onError
(
new
ProxyCacheException
(
"Error closing socket"
,
e
));
}
}
private
void
onError
(
Throwable
e
)
{
LOG
.
error
(
"HttpProxyCacheServer error"
,
e
);
}
private
final
class
WaitRequestsRunnable
implements
Runnable
{
private
final
CountDownLatch
startSignal
;
public
WaitRequestsRunnable
(
CountDownLatch
startSignal
)
{
this
.
startSignal
=
startSignal
;
}
@Override
public
void
run
()
{
startSignal
.
countDown
();
waitForRequest
();
}
}
private
final
class
SocketProcessorRunnable
implements
Runnable
{
private
final
Socket
socket
;
public
SocketProcessorRunnable
(
Socket
socket
)
{
this
.
socket
=
socket
;
}
@Override
public
void
run
()
{
processSocket
(
socket
);
}
}
/**
* Builder for {@link HttpProxyCacheServer}.
*/
public
static
final
class
Builder
{
private
static
final
long
DEFAULT_MAX_SIZE
=
512
*
1024
*
1024
;
private
File
cacheRoot
;
private
FileNameGenerator
fileNameGenerator
;
private
DiskUsage
diskUsage
;
private
SourceInfoStorage
sourceInfoStorage
;
public
Builder
(
Context
context
)
{
this
.
sourceInfoStorage
=
SourceInfoStorageFactory
.
newSourceInfoStorage
(
context
);
this
.
cacheRoot
=
StorageUtils
.
getIndividualCacheDirectory
(
context
);
this
.
diskUsage
=
new
TotalSizeLruDiskUsage
(
DEFAULT_MAX_SIZE
);
this
.
fileNameGenerator
=
new
Md5FileNameGenerator
();
}
/**
* Overrides default cache folder to be used for caching files.
* <p/>
* By default AndroidVideoCache uses
* '/Android/data/[app_package_name]/cache/video-cache/' if card is mounted and app has appropriate permission
* or 'video-cache' subdirectory in default application's cache directory otherwise.
* <p/>
* <b>Note</b> directory must be used <b>only</b> for AndroidVideoCache files.
*
* @param file a cache directory, can't be null.
* @return a builder.
*/
public
Builder
cacheDirectory
(
File
file
)
{
this
.
cacheRoot
=
checkNotNull
(
file
);
return
this
;
}
/**
* Overrides default cache file name generator {@link Md5FileNameGenerator} .
*
* @param fileNameGenerator a new file name generator.
* @return a builder.
*/
public
Builder
fileNameGenerator
(
FileNameGenerator
fileNameGenerator
)
{
this
.
fileNameGenerator
=
checkNotNull
(
fileNameGenerator
);
return
this
;
}
/**
* Sets max cache size in bytes.
* All files that exceeds limit will be deleted using LRU strategy.
* Default value is 512 Mb.
* <p/>
* Note this method overrides result of calling {@link #maxCacheFilesCount(int)}
*
* @param maxSize max cache size in bytes.
* @return a builder.
*/
public
Builder
maxCacheSize
(
long
maxSize
)
{
this
.
diskUsage
=
new
TotalSizeLruDiskUsage
(
maxSize
);
return
this
;
}
/**
* Sets max cache files count.
* All files that exceeds limit will be deleted using LRU strategy.
* <p/>
* Note this method overrides result of calling {@link #maxCacheSize(long)}
*
* @param count max cache files count.
* @return a builder.
*/
public
Builder
maxCacheFilesCount
(
int
count
)
{
this
.
diskUsage
=
new
TotalCountLruDiskUsage
(
count
);
return
this
;
}
/**
* Builds new instance of {@link HttpProxyCacheServer}.
*
* @return proxy cache. Only single instance should be used across whole app.
*/
public
HttpProxyCacheServer
build
()
{
Config
config
=
buildConfig
();
return
new
HttpProxyCacheServer
(
config
);
}
private
Config
buildConfig
()
{
return
new
Config
(
cacheRoot
,
fileNameGenerator
,
diskUsage
,
sourceInfoStorage
);
}
}
}
videoCache/src/main/java/com/danikula/videocache/HttpProxyCacheServerClients.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Message
;
import
com.danikula.videocache.file.FileCache
;
import
java.io.File
;
import
java.io.IOException
;
import
java.net.Socket
;
import
java.util.List
;
import
java.util.concurrent.CopyOnWriteArrayList
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Client for {@link HttpProxyCacheServer}
*
* @author Alexey Danilov (danikula@gmail.com).
*/
final
class
HttpProxyCacheServerClients
{
private
final
AtomicInteger
clientsCount
=
new
AtomicInteger
(
0
);
private
final
String
url
;
private
volatile
HttpProxyCache
proxyCache
;
private
final
List
<
CacheListener
>
listeners
=
new
CopyOnWriteArrayList
<>();
private
final
CacheListener
uiCacheListener
;
private
final
Config
config
;
public
HttpProxyCacheServerClients
(
String
url
,
Config
config
)
{
this
.
url
=
checkNotNull
(
url
);
this
.
config
=
checkNotNull
(
config
);
this
.
uiCacheListener
=
new
UiListenerHandler
(
url
,
listeners
);
}
public
void
processRequest
(
GetRequest
request
,
Socket
socket
)
throws
ProxyCacheException
,
IOException
{
startProcessRequest
();
try
{
clientsCount
.
incrementAndGet
();
proxyCache
.
processRequest
(
request
,
socket
);
}
finally
{
finishProcessRequest
();
}
}
private
synchronized
void
startProcessRequest
()
throws
ProxyCacheException
{
proxyCache
=
proxyCache
==
null
?
newHttpProxyCache
()
:
proxyCache
;
}
private
synchronized
void
finishProcessRequest
()
{
if
(
clientsCount
.
decrementAndGet
()
<=
0
)
{
proxyCache
.
shutdown
();
proxyCache
=
null
;
}
}
public
void
registerCacheListener
(
CacheListener
cacheListener
)
{
listeners
.
add
(
cacheListener
);
}
public
void
unregisterCacheListener
(
CacheListener
cacheListener
)
{
listeners
.
remove
(
cacheListener
);
}
public
void
shutdown
()
{
listeners
.
clear
();
if
(
proxyCache
!=
null
)
{
proxyCache
.
registerCacheListener
(
null
);
proxyCache
.
shutdown
();
proxyCache
=
null
;
}
clientsCount
.
set
(
0
);
}
public
int
getClientsCount
()
{
return
clientsCount
.
get
();
}
private
HttpProxyCache
newHttpProxyCache
()
throws
ProxyCacheException
{
HttpUrlSource
source
=
new
HttpUrlSource
(
url
,
config
.
sourceInfoStorage
);
FileCache
cache
=
new
FileCache
(
config
.
generateCacheFile
(
url
),
config
.
diskUsage
);
HttpProxyCache
httpProxyCache
=
new
HttpProxyCache
(
source
,
cache
);
httpProxyCache
.
registerCacheListener
(
uiCacheListener
);
return
httpProxyCache
;
}
private
static
final
class
UiListenerHandler
extends
Handler
implements
CacheListener
{
private
final
String
url
;
private
final
List
<
CacheListener
>
listeners
;
public
UiListenerHandler
(
String
url
,
List
<
CacheListener
>
listeners
)
{
super
(
Looper
.
getMainLooper
());
this
.
url
=
url
;
this
.
listeners
=
listeners
;
}
@Override
public
void
onCacheAvailable
(
File
file
,
String
url
,
int
percentsAvailable
)
{
Message
message
=
obtainMessage
();
message
.
arg1
=
percentsAvailable
;
message
.
obj
=
file
;
sendMessage
(
message
);
}
@Override
public
void
handleMessage
(
Message
msg
)
{
for
(
CacheListener
cacheListener
:
listeners
)
{
cacheListener
.
onCacheAvailable
((
File
)
msg
.
obj
,
url
,
msg
.
arg1
);
}
}
}
}
videoCache/src/main/java/com/danikula/videocache/HttpUrlSource.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.text.TextUtils
;
import
com.danikula.videocache.sourcestorage.SourceInfoStorage
;
import
com.danikula.videocache.sourcestorage.SourceInfoStorageFactory
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.BufferedInputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InterruptedIOException
;
import
java.net.HttpURLConnection
;
import
java.net.URL
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
import
static
com
.
danikula
.
videocache
.
ProxyCacheUtils
.
DEFAULT_BUFFER_SIZE
;
import
static
java
.
net
.
HttpURLConnection
.
HTTP_MOVED_PERM
;
import
static
java
.
net
.
HttpURLConnection
.
HTTP_MOVED_TEMP
;
import
static
java
.
net
.
HttpURLConnection
.
HTTP_OK
;
import
static
java
.
net
.
HttpURLConnection
.
HTTP_PARTIAL
;
import
static
java
.
net
.
HttpURLConnection
.
HTTP_SEE_OTHER
;
/**
* {@link Source} that uses http resource as source for {@link ProxyCache}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
HttpUrlSource
implements
Source
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"HttpUrlSource"
);
private
static
final
int
MAX_REDIRECTS
=
5
;
private
final
SourceInfoStorage
sourceInfoStorage
;
private
SourceInfo
sourceInfo
;
private
HttpURLConnection
connection
;
private
InputStream
inputStream
;
public
HttpUrlSource
(
String
url
)
{
this
(
url
,
SourceInfoStorageFactory
.
newEmptySourceInfoStorage
());
}
public
HttpUrlSource
(
String
url
,
SourceInfoStorage
sourceInfoStorage
)
{
this
.
sourceInfoStorage
=
checkNotNull
(
sourceInfoStorage
);
SourceInfo
sourceInfo
=
sourceInfoStorage
.
get
(
url
);
this
.
sourceInfo
=
sourceInfo
!=
null
?
sourceInfo
:
new
SourceInfo
(
url
,
Integer
.
MIN_VALUE
,
ProxyCacheUtils
.
getSupposablyMime
(
url
));
}
public
HttpUrlSource
(
HttpUrlSource
source
)
{
this
.
sourceInfo
=
source
.
sourceInfo
;
this
.
sourceInfoStorage
=
source
.
sourceInfoStorage
;
}
@Override
public
synchronized
int
length
()
throws
ProxyCacheException
{
if
(
sourceInfo
.
length
==
Integer
.
MIN_VALUE
)
{
fetchContentInfo
();
}
return
sourceInfo
.
length
;
}
@Override
public
void
open
(
int
offset
)
throws
ProxyCacheException
{
try
{
connection
=
openConnection
(
offset
,
-
1
);
String
mime
=
connection
.
getContentType
();
inputStream
=
new
BufferedInputStream
(
connection
.
getInputStream
(),
DEFAULT_BUFFER_SIZE
);
int
length
=
readSourceAvailableBytes
(
connection
,
offset
,
connection
.
getResponseCode
());
this
.
sourceInfo
=
new
SourceInfo
(
sourceInfo
.
url
,
length
,
mime
);
this
.
sourceInfoStorage
.
put
(
sourceInfo
.
url
,
sourceInfo
);
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error opening connection for "
+
sourceInfo
.
url
+
" with offset "
+
offset
,
e
);
}
}
private
int
readSourceAvailableBytes
(
HttpURLConnection
connection
,
int
offset
,
int
responseCode
)
throws
IOException
{
int
contentLength
=
connection
.
getContentLength
();
return
responseCode
==
HTTP_OK
?
contentLength
:
responseCode
==
HTTP_PARTIAL
?
contentLength
+
offset
:
sourceInfo
.
length
;
}
@Override
public
void
close
()
throws
ProxyCacheException
{
if
(
connection
!=
null
)
{
try
{
connection
.
disconnect
();
}
catch
(
NullPointerException
|
IllegalArgumentException
e
)
{
String
message
=
"Wait... but why? WTF!? "
+
"Really shouldn't happen any more after fixing https://github.com/danikula/AndroidVideoCache/issues/43. "
+
"If you read it on your device log, please, notify me danikula@gmail.com or create issue here https://github.com/danikula/AndroidVideoCache/issues."
;
throw
new
RuntimeException
(
message
,
e
);
}
}
}
@Override
public
int
read
(
byte
[]
buffer
)
throws
ProxyCacheException
{
if
(
inputStream
==
null
)
{
throw
new
ProxyCacheException
(
"Error reading data from "
+
sourceInfo
.
url
+
": connection is absent!"
);
}
try
{
return
inputStream
.
read
(
buffer
,
0
,
buffer
.
length
);
}
catch
(
InterruptedIOException
e
)
{
throw
new
InterruptedProxyCacheException
(
"Reading source "
+
sourceInfo
.
url
+
" is interrupted"
,
e
);
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error reading data from "
+
sourceInfo
.
url
,
e
);
}
}
private
void
fetchContentInfo
()
throws
ProxyCacheException
{
LOG
.
debug
(
"Read content info from "
+
sourceInfo
.
url
);
HttpURLConnection
urlConnection
=
null
;
InputStream
inputStream
=
null
;
try
{
urlConnection
=
openConnection
(
0
,
10000
);
int
length
=
urlConnection
.
getContentLength
();
String
mime
=
urlConnection
.
getContentType
();
inputStream
=
urlConnection
.
getInputStream
();
this
.
sourceInfo
=
new
SourceInfo
(
sourceInfo
.
url
,
length
,
mime
);
this
.
sourceInfoStorage
.
put
(
sourceInfo
.
url
,
sourceInfo
);
LOG
.
debug
(
"Source info fetched: "
+
sourceInfo
);
}
catch
(
IOException
e
)
{
LOG
.
error
(
"Error fetching info from "
+
sourceInfo
.
url
,
e
);
}
finally
{
ProxyCacheUtils
.
close
(
inputStream
);
if
(
urlConnection
!=
null
)
{
urlConnection
.
disconnect
();
}
}
}
private
HttpURLConnection
openConnection
(
int
offset
,
int
timeout
)
throws
IOException
,
ProxyCacheException
{
HttpURLConnection
connection
;
boolean
redirected
;
int
redirectCount
=
0
;
String
url
=
this
.
sourceInfo
.
url
;
do
{
LOG
.
debug
(
"Open connection "
+
(
offset
>
0
?
" with offset "
+
offset
:
""
)
+
" to "
+
url
);
connection
=
(
HttpURLConnection
)
new
URL
(
url
).
openConnection
();
if
(
offset
>
0
)
{
connection
.
setRequestProperty
(
"Range"
,
"bytes="
+
offset
+
"-"
);
}
if
(
timeout
>
0
)
{
connection
.
setConnectTimeout
(
timeout
);
connection
.
setReadTimeout
(
timeout
);
}
int
code
=
connection
.
getResponseCode
();
redirected
=
code
==
HTTP_MOVED_PERM
||
code
==
HTTP_MOVED_TEMP
||
code
==
HTTP_SEE_OTHER
;
if
(
redirected
)
{
url
=
connection
.
getHeaderField
(
"Location"
);
redirectCount
++;
connection
.
disconnect
();
}
if
(
redirectCount
>
MAX_REDIRECTS
)
{
throw
new
ProxyCacheException
(
"Too many redirects: "
+
redirectCount
);
}
}
while
(
redirected
);
return
connection
;
}
public
synchronized
String
getMime
()
throws
ProxyCacheException
{
if
(
TextUtils
.
isEmpty
(
sourceInfo
.
mime
))
{
fetchContentInfo
();
}
return
sourceInfo
.
mime
;
}
public
String
getUrl
()
{
return
sourceInfo
.
url
;
}
@Override
public
String
toString
()
{
return
"HttpUrlSource{sourceInfo='"
+
sourceInfo
+
"}"
;
}
}
videoCache/src/main/java/com/danikula/videocache/InterruptedProxyCacheException.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
/**
* Indicates interruption error in work of {@link ProxyCache} fired by user.
*
* @author Alexey Danilov
*/
public
class
InterruptedProxyCacheException
extends
ProxyCacheException
{
public
InterruptedProxyCacheException
(
String
message
)
{
super
(
message
);
}
public
InterruptedProxyCacheException
(
String
message
,
Throwable
cause
)
{
super
(
message
,
cause
);
}
public
InterruptedProxyCacheException
(
Throwable
cause
)
{
super
(
cause
);
}
}
videoCache/src/main/java/com/danikula/videocache/Pinger.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.net.Socket
;
import
java.util.Arrays
;
import
java.util.Locale
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.TimeoutException
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkArgument
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
import
static
java
.
util
.
concurrent
.
TimeUnit
.
MILLISECONDS
;
/**
* Pings {@link HttpProxyCacheServer} to make sure it works.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
Pinger
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"Pinger"
);
private
static
final
String
PING_REQUEST
=
"ping"
;
private
static
final
String
PING_RESPONSE
=
"ping ok"
;
private
final
ExecutorService
pingExecutor
=
Executors
.
newSingleThreadExecutor
();
private
final
String
host
;
private
final
int
port
;
Pinger
(
String
host
,
int
port
)
{
this
.
host
=
checkNotNull
(
host
);
this
.
port
=
port
;
}
boolean
ping
(
int
maxAttempts
,
int
startTimeout
)
{
checkArgument
(
maxAttempts
>=
1
);
checkArgument
(
startTimeout
>
0
);
int
timeout
=
startTimeout
;
int
attempts
=
0
;
while
(
attempts
<
maxAttempts
)
{
try
{
Future
<
Boolean
>
pingFuture
=
pingExecutor
.
submit
(
new
PingCallable
());
boolean
pinged
=
pingFuture
.
get
(
timeout
,
MILLISECONDS
);
if
(
pinged
)
{
return
true
;
}
}
catch
(
TimeoutException
e
)
{
LOG
.
warn
(
"Error pinging server (attempt: "
+
attempts
+
", timeout: "
+
timeout
+
"). "
);
}
catch
(
InterruptedException
|
ExecutionException
e
)
{
LOG
.
error
(
"Error pinging server due to unexpected error"
,
e
);
}
attempts
++;
timeout
*=
2
;
}
String
error
=
String
.
format
(
"Error pinging server (attempts: %d, max timeout: %d). "
+
"If you see this message, please, email me danikula@gmail.com "
+
"or create issue here https://github.com/danikula/AndroidVideoCache/issues"
,
attempts
,
timeout
/
2
);
LOG
.
error
(
error
,
new
ProxyCacheException
(
error
));
return
false
;
}
boolean
isPingRequest
(
String
request
)
{
return
PING_REQUEST
.
equals
(
request
);
}
void
responseToPing
(
Socket
socket
)
throws
IOException
{
OutputStream
out
=
socket
.
getOutputStream
();
out
.
write
(
"HTTP/1.1 200 OK\n\n"
.
getBytes
());
out
.
write
(
PING_RESPONSE
.
getBytes
());
}
private
boolean
pingServer
()
throws
ProxyCacheException
{
String
pingUrl
=
getPingUrl
();
HttpUrlSource
source
=
new
HttpUrlSource
(
pingUrl
);
try
{
byte
[]
expectedResponse
=
PING_RESPONSE
.
getBytes
();
source
.
open
(
0
);
byte
[]
response
=
new
byte
[
expectedResponse
.
length
];
source
.
read
(
response
);
boolean
pingOk
=
Arrays
.
equals
(
expectedResponse
,
response
);
LOG
.
info
(
"Ping response: `"
+
new
String
(
response
)
+
"`, pinged? "
+
pingOk
);
return
pingOk
;
}
catch
(
ProxyCacheException
e
)
{
LOG
.
error
(
"Error reading ping response"
,
e
);
return
false
;
}
finally
{
source
.
close
();
}
}
private
String
getPingUrl
()
{
return
String
.
format
(
Locale
.
US
,
"http://%s:%d/%s"
,
host
,
port
,
PING_REQUEST
);
}
private
class
PingCallable
implements
Callable
<
Boolean
>
{
@Override
public
Boolean
call
()
throws
Exception
{
return
pingServer
();
}
}
}
videoCache/src/main/java/com/danikula/videocache/Preconditions.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
public
final
class
Preconditions
{
public
static
<
T
>
T
checkNotNull
(
T
reference
)
{
if
(
reference
==
null
)
{
throw
new
NullPointerException
();
}
return
reference
;
}
public
static
void
checkAllNotNull
(
Object
...
references
)
{
for
(
Object
reference
:
references
)
{
if
(
reference
==
null
)
{
throw
new
NullPointerException
();
}
}
}
public
static
<
T
>
T
checkNotNull
(
T
reference
,
String
errorMessage
)
{
if
(
reference
==
null
)
{
throw
new
NullPointerException
(
errorMessage
);
}
return
reference
;
}
static
void
checkArgument
(
boolean
expression
)
{
if
(!
expression
)
{
throw
new
IllegalArgumentException
();
}
}
static
void
checkArgument
(
boolean
expression
,
String
errorMessage
)
{
if
(!
expression
)
{
throw
new
IllegalArgumentException
(
errorMessage
);
}
}
}
videoCache/src/main/java/com/danikula/videocache/ProxyCache.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Proxy for {@link Source} with caching support ({@link Cache}).
* <p/>
* Can be used only for sources with persistent data (that doesn't change with time).
* Method {@link #read(byte[], long, int)} will be blocked while fetching data from source.
* Useful for streaming something with caching e.g. streaming video/audio etc.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
ProxyCache
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"ProxyCache"
);
private
static
final
int
MAX_READ_SOURCE_ATTEMPTS
=
1
;
private
final
Source
source
;
private
final
Cache
cache
;
private
final
Object
wc
=
new
Object
();
private
final
Object
stopLock
=
new
Object
();
private
final
AtomicInteger
readSourceErrorsCount
;
private
volatile
Thread
sourceReaderThread
;
private
volatile
boolean
stopped
;
private
volatile
int
percentsAvailable
=
-
1
;
public
ProxyCache
(
Source
source
,
Cache
cache
)
{
this
.
source
=
checkNotNull
(
source
);
this
.
cache
=
checkNotNull
(
cache
);
this
.
readSourceErrorsCount
=
new
AtomicInteger
();
}
public
int
read
(
byte
[]
buffer
,
long
offset
,
int
length
)
throws
ProxyCacheException
{
ProxyCacheUtils
.
assertBuffer
(
buffer
,
offset
,
length
);
while
(!
cache
.
isCompleted
()
&&
cache
.
available
()
<
(
offset
+
length
)
&&
!
stopped
)
{
readSourceAsync
();
waitForSourceData
();
checkReadSourceErrorsCount
();
}
int
read
=
cache
.
read
(
buffer
,
offset
,
length
);
if
(
cache
.
isCompleted
()
&&
percentsAvailable
!=
100
)
{
percentsAvailable
=
100
;
onCachePercentsAvailableChanged
(
100
);
}
return
read
;
}
private
void
checkReadSourceErrorsCount
()
throws
ProxyCacheException
{
int
errorsCount
=
readSourceErrorsCount
.
get
();
if
(
errorsCount
>=
MAX_READ_SOURCE_ATTEMPTS
)
{
readSourceErrorsCount
.
set
(
0
);
throw
new
ProxyCacheException
(
"Error reading source "
+
errorsCount
+
" times"
);
}
}
public
void
shutdown
()
{
synchronized
(
stopLock
)
{
LOG
.
debug
(
"Shutdown proxy for "
+
source
);
try
{
stopped
=
true
;
if
(
sourceReaderThread
!=
null
)
{
sourceReaderThread
.
interrupt
();
}
cache
.
close
();
}
catch
(
ProxyCacheException
e
)
{
onError
(
e
);
}
}
}
private
synchronized
void
readSourceAsync
()
throws
ProxyCacheException
{
boolean
readingInProgress
=
sourceReaderThread
!=
null
&&
sourceReaderThread
.
getState
()
!=
Thread
.
State
.
TERMINATED
;
if
(!
stopped
&&
!
cache
.
isCompleted
()
&&
!
readingInProgress
)
{
sourceReaderThread
=
new
Thread
(
new
SourceReaderRunnable
(),
"Source reader for "
+
source
);
sourceReaderThread
.
start
();
}
}
private
void
waitForSourceData
()
throws
ProxyCacheException
{
synchronized
(
wc
)
{
try
{
wc
.
wait
(
1000
);
}
catch
(
InterruptedException
e
)
{
throw
new
ProxyCacheException
(
"Waiting source data is interrupted!"
,
e
);
}
}
}
private
void
notifyNewCacheDataAvailable
(
long
cacheAvailable
,
long
sourceAvailable
)
{
onCacheAvailable
(
cacheAvailable
,
sourceAvailable
);
synchronized
(
wc
)
{
wc
.
notifyAll
();
}
}
protected
void
onCacheAvailable
(
long
cacheAvailable
,
long
sourceLength
)
{
boolean
zeroLengthSource
=
sourceLength
==
0
;
int
percents
=
zeroLengthSource
?
100
:
(
int
)
(
cacheAvailable
*
100
/
sourceLength
);
boolean
percentsChanged
=
percents
!=
percentsAvailable
;
boolean
sourceLengthKnown
=
sourceLength
>=
0
;
if
(
sourceLengthKnown
&&
percentsChanged
)
{
onCachePercentsAvailableChanged
(
percents
);
}
percentsAvailable
=
percents
;
}
protected
void
onCachePercentsAvailableChanged
(
int
percentsAvailable
)
{
}
private
void
readSource
()
{
int
sourceAvailable
=
-
1
;
int
offset
=
0
;
try
{
offset
=
cache
.
available
();
source
.
open
(
offset
);
sourceAvailable
=
source
.
length
();
byte
[]
buffer
=
new
byte
[
ProxyCacheUtils
.
DEFAULT_BUFFER_SIZE
];
int
readBytes
;
while
((
readBytes
=
source
.
read
(
buffer
))
!=
-
1
)
{
synchronized
(
stopLock
)
{
if
(
isStopped
())
{
return
;
}
cache
.
append
(
buffer
,
readBytes
);
}
offset
+=
readBytes
;
notifyNewCacheDataAvailable
(
offset
,
sourceAvailable
);
}
tryComplete
();
onSourceRead
();
}
catch
(
Throwable
e
)
{
readSourceErrorsCount
.
incrementAndGet
();
onError
(
e
);
}
finally
{
closeSource
();
notifyNewCacheDataAvailable
(
offset
,
sourceAvailable
);
}
}
private
void
onSourceRead
()
{
// guaranteed notify listeners after source read and cache completed
percentsAvailable
=
100
;
onCachePercentsAvailableChanged
(
percentsAvailable
);
}
private
void
tryComplete
()
throws
ProxyCacheException
{
synchronized
(
stopLock
)
{
if
(!
isStopped
()
&&
cache
.
available
()
==
source
.
length
())
{
cache
.
complete
();
}
}
}
private
boolean
isStopped
()
{
return
Thread
.
currentThread
().
isInterrupted
()
||
stopped
;
}
private
void
closeSource
()
{
try
{
source
.
close
();
}
catch
(
ProxyCacheException
e
)
{
onError
(
new
ProxyCacheException
(
"Error closing source "
+
source
,
e
));
}
}
protected
final
void
onError
(
final
Throwable
e
)
{
boolean
interruption
=
e
instanceof
InterruptedProxyCacheException
;
if
(
interruption
)
{
LOG
.
debug
(
"ProxyCache is interrupted"
);
}
else
{
LOG
.
error
(
"ProxyCache error"
,
e
);
}
}
private
class
SourceReaderRunnable
implements
Runnable
{
@Override
public
void
run
()
{
readSource
();
}
}
}
videoCache/src/main/java/com/danikula/videocache/ProxyCacheException.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
/**
* Indicates any error in work of {@link ProxyCache}.
*
* @author Alexey Danilov
*/
public
class
ProxyCacheException
extends
Exception
{
public
ProxyCacheException
(
String
message
)
{
super
(
message
);
}
public
ProxyCacheException
(
String
message
,
Throwable
cause
)
{
super
(
message
,
cause
);
}
public
ProxyCacheException
(
Throwable
cause
)
{
super
(
cause
);
}
}
videoCache/src/main/java/com/danikula/videocache/ProxyCacheUtils.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.text.TextUtils
;
import
android.webkit.MimeTypeMap
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.Closeable
;
import
java.io.IOException
;
import
java.io.UnsupportedEncodingException
;
import
java.net.URLDecoder
;
import
java.net.URLEncoder
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.Arrays
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkArgument
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Just simple utils.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
ProxyCacheUtils
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"ProxyCacheUtils"
);
static
final
int
DEFAULT_BUFFER_SIZE
=
8
*
1024
;
static
final
int
MAX_ARRAY_PREVIEW
=
16
;
static
String
getSupposablyMime
(
String
url
)
{
MimeTypeMap
mimes
=
MimeTypeMap
.
getSingleton
();
String
extension
=
MimeTypeMap
.
getFileExtensionFromUrl
(
url
);
return
TextUtils
.
isEmpty
(
extension
)
?
null
:
mimes
.
getMimeTypeFromExtension
(
extension
);
}
static
void
assertBuffer
(
byte
[]
buffer
,
long
offset
,
int
length
)
{
checkNotNull
(
buffer
,
"Buffer must be not null!"
);
checkArgument
(
offset
>=
0
,
"Data offset must be positive!"
);
checkArgument
(
length
>=
0
&&
length
<=
buffer
.
length
,
"Length must be in range [0..buffer.length]"
);
}
static
String
preview
(
byte
[]
data
,
int
length
)
{
int
previewLength
=
Math
.
min
(
MAX_ARRAY_PREVIEW
,
Math
.
max
(
length
,
0
));
byte
[]
dataRange
=
Arrays
.
copyOfRange
(
data
,
0
,
previewLength
);
String
preview
=
Arrays
.
toString
(
dataRange
);
if
(
previewLength
<
length
)
{
preview
=
preview
.
substring
(
0
,
preview
.
length
()
-
1
)
+
", ...]"
;
}
return
preview
;
}
static
String
encode
(
String
url
)
{
try
{
return
URLEncoder
.
encode
(
url
,
"utf-8"
);
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
RuntimeException
(
"Error encoding url"
,
e
);
}
}
static
String
decode
(
String
url
)
{
try
{
return
URLDecoder
.
decode
(
url
,
"utf-8"
);
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
RuntimeException
(
"Error decoding url"
,
e
);
}
}
static
void
close
(
Closeable
closeable
)
{
if
(
closeable
!=
null
)
{
try
{
closeable
.
close
();
}
catch
(
IOException
e
)
{
LOG
.
error
(
"Error closing resource"
,
e
);
}
}
}
public
static
String
computeMD5
(
String
string
)
{
try
{
MessageDigest
messageDigest
=
MessageDigest
.
getInstance
(
"MD5"
);
byte
[]
digestBytes
=
messageDigest
.
digest
(
string
.
getBytes
());
return
bytesToHexString
(
digestBytes
);
}
catch
(
NoSuchAlgorithmException
e
)
{
throw
new
IllegalStateException
(
e
);
}
}
private
static
String
bytesToHexString
(
byte
[]
bytes
)
{
StringBuffer
sb
=
new
StringBuffer
();
for
(
byte
b
:
bytes
)
{
sb
.
append
(
String
.
format
(
"%02x"
,
b
));
}
return
sb
.
toString
();
}
}
videoCache/src/main/java/com/danikula/videocache/Source.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
/**
* Source for proxy.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
Source
{
/**
* Opens source. Source should be open before using {@link #read(byte[])}
*
* @param offset offset in bytes for source.
* @throws ProxyCacheException if error occur while opening source.
*/
void
open
(
int
offset
)
throws
ProxyCacheException
;
/**
* Returns length bytes or <b>negative value</b> if length is unknown.
*
* @return bytes length
* @throws ProxyCacheException if error occur while fetching source data.
*/
int
length
()
throws
ProxyCacheException
;
/**
* Read data to byte buffer from source with current offset.
*
* @param buffer a buffer to be used for reading data.
* @throws ProxyCacheException if error occur while reading source.
*/
int
read
(
byte
[]
buffer
)
throws
ProxyCacheException
;
/**
* Closes source and release resources. Every opened source should be closed.
*
* @throws ProxyCacheException if error occur while closing source.
*/
void
close
()
throws
ProxyCacheException
;
}
videoCache/src/main/java/com/danikula/videocache/SourceInfo.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
/**
* Stores source's info.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
SourceInfo
{
public
final
String
url
;
public
final
int
length
;
public
final
String
mime
;
public
SourceInfo
(
String
url
,
int
length
,
String
mime
)
{
this
.
url
=
url
;
this
.
length
=
length
;
this
.
mime
=
mime
;
}
@Override
public
String
toString
()
{
return
"SourceInfo{"
+
"url='"
+
url
+
'\''
+
", length="
+
length
+
", mime='"
+
mime
+
'\''
+
'}'
;
}
}
videoCache/src/main/java/com/danikula/videocache/StorageUtils.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache
;
import
android.content.Context
;
import
android.os.Environment
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.File
;
import
static
android
.
os
.
Environment
.
MEDIA_MOUNTED
;
/**
* Provides application storage paths
* <p/>
* See https://github.com/nostra13/Android-Universal-Image-Loader
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
final
class
StorageUtils
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"StorageUtils"
);
private
static
final
String
INDIVIDUAL_DIR_NAME
=
"video-cache"
;
/**
* Returns individual application cache directory (for only video caching from Proxy). Cache directory will be
* created on SD card <i>("/Android/data/[app_package_name]/cache/video-cache")</i> if card is mounted .
* Else - Android defines cache directory on device's file system.
*
* @param context Application context
* @return Cache {@link File directory}
*/
public
static
File
getIndividualCacheDirectory
(
Context
context
)
{
File
cacheDir
=
getCacheDirectory
(
context
,
true
);
return
new
File
(
cacheDir
,
INDIVIDUAL_DIR_NAME
);
}
/**
* Returns application cache directory. Cache directory will be created on SD card
* <i>("/Android/data/[app_package_name]/cache")</i> (if card is mounted and app has appropriate permission) or
* on device's file system depending incoming parameters.
*
* @param context Application context
* @param preferExternal Whether prefer external location for cache
* @return Cache {@link File directory}.<br />
* <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and
* {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
*/
private
static
File
getCacheDirectory
(
Context
context
,
boolean
preferExternal
)
{
File
appCacheDir
=
null
;
String
externalStorageState
;
try
{
externalStorageState
=
Environment
.
getExternalStorageState
();
}
catch
(
NullPointerException
e
)
{
// (sh)it happens
externalStorageState
=
""
;
}
if
(
preferExternal
&&
MEDIA_MOUNTED
.
equals
(
externalStorageState
))
{
appCacheDir
=
getExternalCacheDir
(
context
);
}
if
(
appCacheDir
==
null
)
{
appCacheDir
=
context
.
getCacheDir
();
}
if
(
appCacheDir
==
null
)
{
String
cacheDirPath
=
"/data/data/"
+
context
.
getPackageName
()
+
"/cache/"
;
LOG
.
warn
(
"Can't define system cache directory! '"
+
cacheDirPath
+
"%s' will be used."
);
appCacheDir
=
new
File
(
cacheDirPath
);
}
return
appCacheDir
;
}
private
static
File
getExternalCacheDir
(
Context
context
)
{
File
dataDir
=
new
File
(
new
File
(
Environment
.
getExternalStorageDirectory
(),
"Android"
),
"data"
);
File
appCacheDir
=
new
File
(
new
File
(
dataDir
,
context
.
getPackageName
()),
"cache"
);
if
(!
appCacheDir
.
exists
())
{
if
(!
appCacheDir
.
mkdirs
())
{
LOG
.
warn
(
"Unable to create external cache directory"
);
return
null
;
}
}
return
appCacheDir
;
}
}
videoCache/src/main/java/com/danikula/videocache/file/DiskUsage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
java.io.File
;
import
java.io.IOException
;
/**
* Declares how {@link FileCache} will use disc space.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
DiskUsage
{
void
touch
(
File
file
)
throws
IOException
;
}
videoCache/src/main/java/com/danikula/videocache/file/FileCache.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
com.danikula.videocache.Cache
;
import
com.danikula.videocache.ProxyCacheException
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.RandomAccessFile
;
/**
* {@link Cache} that uses file for storing data.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
FileCache
implements
Cache
{
private
static
final
String
TEMP_POSTFIX
=
".download"
;
private
final
DiskUsage
diskUsage
;
public
File
file
;
private
RandomAccessFile
dataFile
;
public
FileCache
(
File
file
)
throws
ProxyCacheException
{
this
(
file
,
new
UnlimitedDiskUsage
());
}
public
FileCache
(
File
file
,
DiskUsage
diskUsage
)
throws
ProxyCacheException
{
try
{
if
(
diskUsage
==
null
)
{
throw
new
NullPointerException
();
}
this
.
diskUsage
=
diskUsage
;
File
directory
=
file
.
getParentFile
();
Files
.
makeDir
(
directory
);
boolean
completed
=
file
.
exists
();
this
.
file
=
completed
?
file
:
new
File
(
file
.
getParentFile
(),
file
.
getName
()
+
TEMP_POSTFIX
);
this
.
dataFile
=
new
RandomAccessFile
(
this
.
file
,
completed
?
"r"
:
"rw"
);
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error using file "
+
file
+
" as disc cache"
,
e
);
}
}
@Override
public
synchronized
int
available
()
throws
ProxyCacheException
{
try
{
return
(
int
)
dataFile
.
length
();
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error reading length of file "
+
file
,
e
);
}
}
@Override
public
synchronized
int
read
(
byte
[]
buffer
,
long
offset
,
int
length
)
throws
ProxyCacheException
{
try
{
dataFile
.
seek
(
offset
);
return
dataFile
.
read
(
buffer
,
0
,
length
);
}
catch
(
IOException
e
)
{
String
format
=
"Error reading %d bytes with offset %d from file[%d bytes] to buffer[%d bytes]"
;
throw
new
ProxyCacheException
(
String
.
format
(
format
,
length
,
offset
,
available
(),
buffer
.
length
),
e
);
}
}
@Override
public
synchronized
void
append
(
byte
[]
data
,
int
length
)
throws
ProxyCacheException
{
try
{
if
(
isCompleted
())
{
throw
new
ProxyCacheException
(
"Error append cache: cache file "
+
file
+
" is completed!"
);
}
dataFile
.
seek
(
available
());
dataFile
.
write
(
data
,
0
,
length
);
}
catch
(
IOException
e
)
{
String
format
=
"Error writing %d bytes to %s from buffer with size %d"
;
throw
new
ProxyCacheException
(
String
.
format
(
format
,
length
,
dataFile
,
data
.
length
),
e
);
}
}
@Override
public
synchronized
void
close
()
throws
ProxyCacheException
{
try
{
dataFile
.
close
();
diskUsage
.
touch
(
file
);
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error closing file "
+
file
,
e
);
}
}
@Override
public
synchronized
void
complete
()
throws
ProxyCacheException
{
if
(
isCompleted
())
{
return
;
}
close
();
String
fileName
=
file
.
getName
().
substring
(
0
,
file
.
getName
().
length
()
-
TEMP_POSTFIX
.
length
());
File
completedFile
=
new
File
(
file
.
getParentFile
(),
fileName
);
boolean
renamed
=
file
.
renameTo
(
completedFile
);
if
(!
renamed
)
{
throw
new
ProxyCacheException
(
"Error renaming file "
+
file
+
" to "
+
completedFile
+
" for completion!"
);
}
file
=
completedFile
;
try
{
dataFile
=
new
RandomAccessFile
(
file
,
"r"
);
}
catch
(
IOException
e
)
{
throw
new
ProxyCacheException
(
"Error opening "
+
file
+
" as disc cache"
,
e
);
}
}
@Override
public
synchronized
boolean
isCompleted
()
{
return
!
isTempFile
(
file
);
}
/**
* Returns file to be used fo caching. It may as original file passed in constructor as some temp file for not completed cache.
*
* @return file for caching.
*/
public
File
getFile
()
{
return
file
;
}
private
boolean
isTempFile
(
File
file
)
{
return
file
.
getName
().
endsWith
(
TEMP_POSTFIX
);
}
}
videoCache/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
/**
* Generator for files to be used for caching.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
FileNameGenerator
{
String
generate
(
String
url
);
}
videoCache/src/main/java/com/danikula/videocache/file/Files.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.RandomAccessFile
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.LinkedList
;
import
java.util.List
;
/**
* Utils for work with files.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
Files
{
static
void
makeDir
(
File
directory
)
throws
IOException
{
if
(
directory
.
exists
())
{
if
(!
directory
.
isDirectory
())
{
throw
new
IOException
(
"File "
+
directory
+
" is not directory!"
);
}
}
else
{
boolean
isCreated
=
directory
.
mkdirs
();
if
(!
isCreated
)
{
throw
new
IOException
(
String
.
format
(
"Directory %s can't be created"
,
directory
.
getAbsolutePath
()));
}
}
}
static
List
<
File
>
getLruListFiles
(
File
directory
)
{
List
<
File
>
result
=
new
LinkedList
<>();
File
[]
files
=
directory
.
listFiles
();
if
(
files
!=
null
)
{
result
=
Arrays
.
asList
(
files
);
Collections
.
sort
(
result
,
new
LastModifiedComparator
());
}
return
result
;
}
static
void
setLastModifiedNow
(
File
file
)
throws
IOException
{
if
(
file
.
exists
())
{
long
now
=
System
.
currentTimeMillis
();
boolean
modified
=
file
.
setLastModified
(
now
);
// on some devices (e.g. Nexus 5) doesn't work
if
(!
modified
)
{
modify
(
file
);
if
(
file
.
lastModified
()
<
now
)
{
throw
new
IOException
(
"Error set last modified date to "
+
file
);
}
}
}
}
static
void
modify
(
File
file
)
throws
IOException
{
long
size
=
file
.
length
();
if
(
size
==
0
)
{
recreateZeroSizeFile
(
file
);
return
;
}
RandomAccessFile
accessFile
=
new
RandomAccessFile
(
file
,
"rwd"
);
accessFile
.
seek
(
size
-
1
);
byte
lastByte
=
accessFile
.
readByte
();
accessFile
.
seek
(
size
-
1
);
accessFile
.
write
(
lastByte
);
accessFile
.
close
();
}
private
static
void
recreateZeroSizeFile
(
File
file
)
throws
IOException
{
if
(!
file
.
delete
()
||
!
file
.
createNewFile
())
{
throw
new
IOException
(
"Error recreate zero-size file "
+
file
);
}
}
private
static
final
class
LastModifiedComparator
implements
Comparator
<
File
>
{
@Override
public
int
compare
(
File
lhs
,
File
rhs
)
{
return
compareLong
(
lhs
.
lastModified
(),
rhs
.
lastModified
());
}
private
int
compareLong
(
long
first
,
long
second
)
{
return
(
first
<
second
)
?
-
1
:
((
first
==
second
)
?
0
:
1
);
}
}
}
videoCache/src/main/java/com/danikula/videocache/file/LruDiskUsage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
/**
* {@link DiskUsage} that uses LRU (Least Recently Used) strategy to trim cache.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
abstract
class
LruDiskUsage
implements
DiskUsage
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
"LruDiskUsage"
);
private
final
ExecutorService
workerThread
=
Executors
.
newSingleThreadExecutor
();
@Override
public
void
touch
(
File
file
)
throws
IOException
{
workerThread
.
submit
(
new
TouchCallable
(
file
));
}
private
void
touchInBackground
(
File
file
)
throws
IOException
{
Files
.
setLastModifiedNow
(
file
);
List
<
File
>
files
=
Files
.
getLruListFiles
(
file
.
getParentFile
());
trim
(
files
);
}
protected
abstract
boolean
accept
(
File
file
,
long
totalSize
,
int
totalCount
);
private
void
trim
(
List
<
File
>
files
)
{
long
totalSize
=
countTotalSize
(
files
);
int
totalCount
=
files
.
size
();
for
(
File
file
:
files
)
{
boolean
accepted
=
accept
(
file
,
totalSize
,
totalCount
);
if
(!
accepted
)
{
long
fileSize
=
file
.
length
();
boolean
deleted
=
file
.
delete
();
if
(
deleted
)
{
totalCount
--;
totalSize
-=
fileSize
;
LOG
.
info
(
"Cache file "
+
file
+
" is deleted because it exceeds cache limit"
);
}
else
{
LOG
.
error
(
"Error deleting file "
+
file
+
" for trimming cache"
);
}
}
}
}
private
long
countTotalSize
(
List
<
File
>
files
)
{
long
totalSize
=
0
;
for
(
File
file
:
files
)
{
totalSize
+=
file
.
length
();
}
return
totalSize
;
}
private
class
TouchCallable
implements
Callable
<
Void
>
{
private
final
File
file
;
public
TouchCallable
(
File
file
)
{
this
.
file
=
file
;
}
@Override
public
Void
call
()
throws
Exception
{
touchInBackground
(
file
);
return
null
;
}
}
}
videoCache/src/main/java/com/danikula/videocache/file/Md5FileNameGenerator.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
android.text.TextUtils
;
import
com.danikula.videocache.ProxyCacheUtils
;
/**
* Implementation of {@link FileNameGenerator} that uses MD5 of url as file name
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
Md5FileNameGenerator
implements
FileNameGenerator
{
private
static
final
int
MAX_EXTENSION_LENGTH
=
4
;
@Override
public
String
generate
(
String
url
)
{
String
extension
=
getExtension
(
url
);
String
name
=
ProxyCacheUtils
.
computeMD5
(
url
);
return
TextUtils
.
isEmpty
(
extension
)
?
name
:
name
+
"."
+
extension
;
}
private
String
getExtension
(
String
url
)
{
int
dotIndex
=
url
.
lastIndexOf
(
'.'
);
int
slashIndex
=
url
.
lastIndexOf
(
'/'
);
return
dotIndex
!=
-
1
&&
dotIndex
>
slashIndex
&&
dotIndex
+
2
+
MAX_EXTENSION_LENGTH
>
url
.
length
()
?
url
.
substring
(
dotIndex
+
1
,
url
.
length
())
:
""
;
}
}
videoCache/src/main/java/com/danikula/videocache/file/TotalCountLruDiskUsage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
java.io.File
;
/**
* {@link DiskUsage} that uses LRU (Least Recently Used) strategy and trims cache size to max files count if needed.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
TotalCountLruDiskUsage
extends
LruDiskUsage
{
private
final
int
maxCount
;
public
TotalCountLruDiskUsage
(
int
maxCount
)
{
if
(
maxCount
<=
0
)
{
throw
new
IllegalArgumentException
(
"Max count must be positive number!"
);
}
this
.
maxCount
=
maxCount
;
}
@Override
protected
boolean
accept
(
File
file
,
long
totalSize
,
int
totalCount
)
{
return
totalCount
<=
maxCount
;
}
}
videoCache/src/main/java/com/danikula/videocache/file/TotalSizeLruDiskUsage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
java.io.File
;
/**
* {@link DiskUsage} that uses LRU (Least Recently Used) strategy and trims cache size to max size if needed.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
TotalSizeLruDiskUsage
extends
LruDiskUsage
{
private
final
long
maxSize
;
public
TotalSizeLruDiskUsage
(
long
maxSize
)
{
if
(
maxSize
<=
0
)
{
throw
new
IllegalArgumentException
(
"Max size must be positive number!"
);
}
this
.
maxSize
=
maxSize
;
}
@Override
protected
boolean
accept
(
File
file
,
long
totalSize
,
int
totalCount
)
{
return
totalSize
<=
maxSize
;
}
}
videoCache/src/main/java/com/danikula/videocache/file/UnlimitedDiskUsage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.file
;
import
java.io.File
;
import
java.io.IOException
;
/**
* Unlimited version of {@link DiskUsage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
UnlimitedDiskUsage
implements
DiskUsage
{
@Override
public
void
touch
(
File
file
)
throws
IOException
{
// do nothing
}
}
videoCache/src/main/java/com/danikula/videocache/sourcestorage/DatabaseSourceInfoStorage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.sourcestorage
;
import
android.content.ContentValues
;
import
android.content.Context
;
import
android.database.Cursor
;
import
android.database.sqlite.SQLiteDatabase
;
import
android.database.sqlite.SQLiteOpenHelper
;
import
com.danikula.videocache.SourceInfo
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkAllNotNull
;
import
static
com
.
danikula
.
videocache
.
Preconditions
.
checkNotNull
;
/**
* Database based {@link SourceInfoStorage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
class
DatabaseSourceInfoStorage
extends
SQLiteOpenHelper
implements
SourceInfoStorage
{
private
static
final
String
TABLE
=
"SourceInfo"
;
private
static
final
String
COLUMN_ID
=
"_id"
;
private
static
final
String
COLUMN_URL
=
"url"
;
private
static
final
String
COLUMN_LENGTH
=
"length"
;
private
static
final
String
COLUMN_MIME
=
"mime"
;
private
static
final
String
[]
ALL_COLUMNS
=
new
String
[]{
COLUMN_ID
,
COLUMN_URL
,
COLUMN_LENGTH
,
COLUMN_MIME
};
private
static
final
String
CREATE_SQL
=
"CREATE TABLE "
+
TABLE
+
" ("
+
COLUMN_ID
+
" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+
COLUMN_URL
+
" TEXT NOT NULL,"
+
COLUMN_MIME
+
" TEXT,"
+
COLUMN_LENGTH
+
" INTEGER"
+
");"
;
DatabaseSourceInfoStorage
(
Context
context
)
{
super
(
context
,
"AndroidVideoCache.db"
,
null
,
1
);
checkNotNull
(
context
);
}
@Override
public
void
onCreate
(
SQLiteDatabase
db
)
{
checkNotNull
(
db
);
db
.
execSQL
(
CREATE_SQL
);
}
@Override
public
void
onUpgrade
(
SQLiteDatabase
db
,
int
oldVersion
,
int
newVersion
)
{
throw
new
IllegalStateException
(
"Should not be called. There is no any migration"
);
}
@Override
public
SourceInfo
get
(
String
url
)
{
checkNotNull
(
url
);
Cursor
cursor
=
null
;
try
{
cursor
=
getReadableDatabase
().
query
(
TABLE
,
ALL_COLUMNS
,
COLUMN_URL
+
"=?"
,
new
String
[]{
url
},
null
,
null
,
null
);
return
cursor
==
null
||
!
cursor
.
moveToFirst
()
?
null
:
convert
(
cursor
);
}
finally
{
if
(
cursor
!=
null
)
{
cursor
.
close
();
}
}
}
@Override
public
void
put
(
String
url
,
SourceInfo
sourceInfo
)
{
checkAllNotNull
(
url
,
sourceInfo
);
SourceInfo
sourceInfoFromDb
=
get
(
url
);
boolean
exist
=
sourceInfoFromDb
!=
null
;
ContentValues
contentValues
=
convert
(
sourceInfo
);
if
(
exist
)
{
getWritableDatabase
().
update
(
TABLE
,
contentValues
,
COLUMN_URL
+
"=?"
,
new
String
[]{
url
});
}
else
{
getWritableDatabase
().
insert
(
TABLE
,
null
,
contentValues
);
}
}
@Override
public
void
release
()
{
close
();
}
private
SourceInfo
convert
(
Cursor
cursor
)
{
return
new
SourceInfo
(
cursor
.
getString
(
cursor
.
getColumnIndexOrThrow
(
COLUMN_URL
)),
cursor
.
getInt
(
cursor
.
getColumnIndexOrThrow
(
COLUMN_LENGTH
)),
cursor
.
getString
(
cursor
.
getColumnIndexOrThrow
(
COLUMN_MIME
))
);
}
private
ContentValues
convert
(
SourceInfo
sourceInfo
)
{
ContentValues
values
=
new
ContentValues
();
values
.
put
(
COLUMN_URL
,
sourceInfo
.
url
);
values
.
put
(
COLUMN_LENGTH
,
sourceInfo
.
length
);
values
.
put
(
COLUMN_MIME
,
sourceInfo
.
mime
);
return
values
;
}
}
videoCache/src/main/java/com/danikula/videocache/sourcestorage/NoSourceInfoStorage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.sourcestorage
;
import
com.danikula.videocache.SourceInfo
;
/**
* {@link SourceInfoStorage} that does nothing.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
NoSourceInfoStorage
implements
SourceInfoStorage
{
@Override
public
SourceInfo
get
(
String
url
)
{
return
null
;
}
@Override
public
void
put
(
String
url
,
SourceInfo
sourceInfo
)
{
}
@Override
public
void
release
()
{
}
}
videoCache/src/main/java/com/danikula/videocache/sourcestorage/SourceInfoStorage.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.sourcestorage
;
import
com.danikula.videocache.SourceInfo
;
/**
* Storage for {@link SourceInfo}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
interface
SourceInfoStorage
{
SourceInfo
get
(
String
url
);
void
put
(
String
url
,
SourceInfo
sourceInfo
);
void
release
();
}
videoCache/src/main/java/com/danikula/videocache/sourcestorage/SourceInfoStorageFactory.java
已删除
100644 → 0
浏览文件 @
22eaaaa7
package
com.danikula.videocache.sourcestorage
;
import
android.content.Context
;
/**
* Simple factory for {@link SourceInfoStorage}.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public
class
SourceInfoStorageFactory
{
public
static
SourceInfoStorage
newSourceInfoStorage
(
Context
context
)
{
return
new
DatabaseSourceInfoStorage
(
context
);
}
public
static
SourceInfoStorage
newEmptySourceInfoStorage
()
{
return
new
NoSourceInfoStorage
();
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录