Skip to content

Commit 679a749

Browse files
jhomlalathemadmrj
andauthored
Feature/april changes (jhomlala#426)
* Updated changelog * Video fit fixes * Fixed set speed iOS implementation * Fixed set speed iOS issue * Fixed Android's notification image OOM issue. * Fixed Android's notification image OOM issue. * Fixed 0 second delay issue in playlist. * Fixed 0 second delay issue in playlist. * 0.0.64 version update, updated changelog * Added pre-caching feature on Android (jhomlala#397) * Added pre-caching feature on Android * Improved pre-cache error handling * Fixes video painted out of bounds bug * Refactored to remove duplicates Co-authored-by: Jakub <jhomlala@gmail.com> * Pre cache refactor * Pre cache refactor * General refactor * General refactor * General refactor Co-authored-by: themadmrj <themadmrj@users.noreply.github.com>
1 parent e0f0b54 commit 679a749

21 files changed

+489
-79
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 0.0.64
2+
* Added Turkish translations (by https://github.com/smurat).
3+
* Video fit fixes (by https://github.com/themadmrj).
4+
* Fixed speed iOS issue.
5+
* Fixed Android's notification image OOM issue.
6+
* Fixed 0 second delay issue in playlist.
7+
* Fixed drmHeaders to be sent in headers rather than request body (by https://github.com/FlutterSu)
8+
* Added preCache, stopPreCache method in BetterPlayerController (coauthored with: https://github.com/themadmrj)
9+
* [BREAKING_CHANGE] clearCache method doesn't require to setup data source in order to use.
10+
111
## 0.0.63
212
* Fixed pause method in dispose.
313
* Added clearCache method in BetterPlayerController.

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ This plugin is based on [Chewie](https://github.com/brianegan/chewie). Chewie is
9494

9595
```yaml
9696
dependencies:
97-
better_player: ^0.0.63
97+
better_player: ^0.0.64
9898
```
9999
100100
2. Install it
@@ -1061,6 +1061,22 @@ Default value is false.
10611061
### More documentation
10621062
https://pub.dev/documentation/better_player/latest/better_player/better_player-library.html
10631063

1064+
### Cache
1065+
Clear all cached data:
1066+
1067+
```dart
1068+
betterPlayerController.clearCache();
1069+
```
1070+
Start pre cache before playing video (android only):
1071+
```dart
1072+
betterPlayerController.preCache(_betterPlayerDataSource);
1073+
```dart
1074+
1075+
Stop running pre cache (android only):
1076+
```dart
1077+
betterPlayerController.stopPreCache(_betterPlayerDataSource);
1078+
```dart
1079+
10641080
10651081
10661082

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ android {
5050
implementation "android.arch.lifecycle:common-java8:1.1.1"
5151
implementation 'androidx.annotation:annotation:1.1.0'
5252
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
53+
implementation "androidx.work:work-runtime:2.5.0"
5354
}
5455
}
5556

android/src/main/java/com/jhomlala/better_player/BetterPlayer.java

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
44
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
5+
import static com.jhomlala.better_player.DataSourceUtils.getDataSourceFactory;
6+
import static com.jhomlala.better_player.DataSourceUtils.getUserAgent;
57

68
import android.app.NotificationChannel;
79
import android.app.NotificationManager;
@@ -61,13 +63,15 @@
6163
import androidx.annotation.Nullable;
6264

6365
import androidx.media.session.MediaButtonReceiver;
66+
import androidx.work.Data;
67+
import androidx.work.OneTimeWorkRequest;
68+
import androidx.work.WorkManager;
6469

6570
import io.flutter.plugin.common.EventChannel;
6671
import io.flutter.plugin.common.MethodChannel.Result;
6772
import io.flutter.view.TextureRegistry;
6873

6974
import java.io.File;
70-
import java.io.IOException;
7175
import java.io.InputStream;
7276
import java.net.HttpURLConnection;
7377
import java.net.URL;
@@ -87,9 +91,8 @@ final class BetterPlayer {
8791
private static final String FORMAT_HLS = "hls";
8892
private static final String FORMAT_OTHER = "other";
8993
private static final String DEFAULT_NOTIFICATION_CHANNEL = "BETTER_PLAYER_NOTIFICATION";
90-
private static final String USER_AGENT = "User-Agent";
91-
private static final String USER_AGENT_PROPERTY = "http.agent";
9294
private static final int NOTIFICATION_ID = 20772077;
95+
private static final int DEFAULT_NOTIFICATION_IMAGE_SIZE_PX = 256;
9396

9497
private final SimpleExoPlayer exoPlayer;
9598
private final TextureRegistry.SurfaceTextureEntry textureEntry;
@@ -132,13 +135,7 @@ void setDataSource(
132135
Uri uri = Uri.parse(dataSource);
133136
DataSource.Factory dataSourceFactory;
134137

135-
String userAgent = System.getProperty(USER_AGENT_PROPERTY);
136-
if (headers != null && headers.containsKey(USER_AGENT)) {
137-
String userAgentHeader = headers.get(USER_AGENT);
138-
if (userAgentHeader != null) {
139-
userAgent = userAgentHeader;
140-
}
141-
}
138+
String userAgent = getUserAgent(headers);
142139

143140
if (licenseUrl != null && !licenseUrl.isEmpty()) {
144141
HttpMediaDrmCallback httpMediaDrmCallback =
@@ -175,16 +172,8 @@ void setDataSource(
175172
drmSessionManager = null;
176173
}
177174

178-
if (isHTTP(uri)) {
179-
dataSourceFactory = new DefaultHttpDataSource.Factory()
180-
.setUserAgent(userAgent)
181-
.setAllowCrossProtocolRedirects(true)
182-
.setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS)
183-
.setReadTimeoutMs(DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS);
184-
185-
if (headers != null) {
186-
((DefaultHttpDataSource.Factory) dataSourceFactory).setDefaultRequestProperties(headers);
187-
}
175+
if (DataSourceUtils.isHTTP(uri)) {
176+
dataSourceFactory = getDataSourceFactory(userAgent, headers);
188177

189178
if (useCache && maxCacheSize > 0 && maxCacheFileSize > 0) {
190179
dataSourceFactory =
@@ -410,36 +399,72 @@ public void disposeRemoteNotifications() {
410399
bitmap = null;
411400
}
412401

413-
private static Bitmap getBitmapFromInternalURL(String src) {
402+
private Bitmap getBitmapFromInternalURL(String src) {
414403
try {
404+
final BitmapFactory.Options options = new BitmapFactory.Options();
405+
options.inJustDecodeBounds = true;
406+
options.inSampleSize = calculateBitmapInSmapleSize(options,
407+
DEFAULT_NOTIFICATION_IMAGE_SIZE_PX,
408+
DEFAULT_NOTIFICATION_IMAGE_SIZE_PX);
409+
options.inJustDecodeBounds = false;
415410
return BitmapFactory.decodeFile(src);
416411
} catch (Exception exception) {
412+
Log.e(TAG, "Failed to get bitmap from internal url: " + src);
417413
return null;
418414
}
419415
}
420416

421-
private static Bitmap getBitmapFromExternalURL(String src) {
417+
418+
private Bitmap getBitmapFromExternalURL(String src) {
419+
InputStream inputStream = null;
422420
try {
423421
URL url = new URL(src);
424422
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
425-
connection.setDoInput(true);
426-
connection.connect();
427-
InputStream input = connection.getInputStream();
428-
return BitmapFactory.decodeStream(input);
429-
} catch (IOException exception) {
423+
inputStream = connection.getInputStream();
424+
425+
final BitmapFactory.Options options = new BitmapFactory.Options();
426+
options.inJustDecodeBounds = true;
427+
BitmapFactory.decodeStream(inputStream, null, options);
428+
inputStream.close();
429+
connection = (HttpURLConnection) url.openConnection();
430+
inputStream = connection.getInputStream();
431+
options.inSampleSize = calculateBitmapInSmapleSize(
432+
options, DEFAULT_NOTIFICATION_IMAGE_SIZE_PX, DEFAULT_NOTIFICATION_IMAGE_SIZE_PX);
433+
options.inJustDecodeBounds = false;
434+
return BitmapFactory.decodeStream(inputStream, null, options);
435+
436+
} catch (Exception exception) {
437+
Log.e(TAG, "Failed to get bitmap from external url: " + src);
430438
return null;
439+
} finally {
440+
try {
441+
if (inputStream != null) {
442+
inputStream.close();
443+
}
444+
} catch (Exception exception) {
445+
Log.e(TAG, "Failed to close bitmap input stream/");
446+
}
431447
}
432448
}
433449

450+
private int calculateBitmapInSmapleSize(
451+
BitmapFactory.Options options, int reqWidth, int reqHeight) {
452+
final int height = options.outHeight;
453+
final int width = options.outWidth;
454+
int inSampleSize = 1;
434455

435-
private static boolean isHTTP(Uri uri) {
436-
if (uri == null || uri.getScheme() == null) {
437-
return false;
456+
if (height > reqHeight || width > reqWidth) {
457+
final int halfHeight = height / 2;
458+
final int halfWidth = width / 2;
459+
while ((halfHeight / inSampleSize) >= reqHeight
460+
&& (halfWidth / inSampleSize) >= reqWidth) {
461+
inSampleSize *= 2;
462+
}
438463
}
439-
String scheme = uri.getScheme();
440-
return scheme.equals("http") || scheme.equals("https");
464+
return inSampleSize;
441465
}
442466

467+
443468
private MediaSource buildMediaSource(
444469
Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) {
445470
int type;
@@ -801,15 +826,44 @@ public void setMixWithOthers(Boolean mixWithOthers) {
801826

802827
//Clear cache without accessing BetterPlayerCache.
803828
@SuppressWarnings("ResultOfMethodCallIgnored")
804-
public void clearCache(Context context) {
829+
public static void clearCache(Context context, Result result) {
805830
try {
806831
File file = context.getCacheDir();
807832
if (file != null) {
808833
file.delete();
809834
}
835+
result.success(null);
810836
} catch (Exception exception) {
811-
Log.e("Cache", exception.toString());
837+
Log.e(TAG, exception.toString());
838+
result.error("", "", "");
839+
}
840+
}
841+
842+
//Start pre cache of video. Invoke work manager job and start caching in background.
843+
static void preCache(Context context, String dataSource, long preCacheSize,
844+
long maxCacheSize, long maxCacheFileSize, Map<String, String> headers,
845+
Result result) {
846+
Data.Builder dataBuilder = new Data.Builder()
847+
.putString(BetterPlayerPlugin.URL_PARAMETER, dataSource)
848+
.putLong(BetterPlayerPlugin.PRE_CACHE_SIZE_PARAMETER, preCacheSize)
849+
.putLong(BetterPlayerPlugin.MAX_CACHE_SIZE_PARAMETER, maxCacheSize)
850+
.putLong(BetterPlayerPlugin.MAX_CACHE_FILE_SIZE_PARAMETER, maxCacheFileSize);
851+
for (String headerKey : headers.keySet()) {
852+
dataBuilder.putString(BetterPlayerPlugin.HEADER_PARAMETER + headerKey, headers.get(headerKey));
812853
}
854+
855+
OneTimeWorkRequest cacheWorkRequest = new OneTimeWorkRequest.Builder(CacheWorker.class)
856+
.addTag(dataSource)
857+
.setInputData(dataBuilder.build()).build();
858+
WorkManager.getInstance(context).enqueue(cacheWorkRequest);
859+
result.success(null);
860+
}
861+
862+
//Stop pre cache of video with given url. If there's no work manager job for given url, then
863+
//it will be ignored.
864+
static void stopPreCache(Context context, String url, Result result) {
865+
WorkManager.getInstance(context).cancelAllWorkByTag(url);
866+
result.success(null);
813867
}
814868

815869
void dispose() {

android/src/main/java/com/jhomlala/better_player/BetterPlayerPlugin.java

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public class BetterPlayerPlugin implements FlutterPlugin, ActivityAware, MethodC
4242
private static final String KEY_PARAMETER = "key";
4343
private static final String HEADERS_PARAMETER = "headers";
4444
private static final String USE_CACHE_PARAMETER = "useCache";
45-
private static final String MAX_CACHE_SIZE_PARAMETER = "maxCacheSize";
46-
private static final String MAX_CACHE_FILE_SIZE_PARAMETER = "maxCacheFileSize";
45+
46+
4747
private static final String ASSET_PARAMETER = "asset";
4848
private static final String PACKAGE_PARAMETER = "package";
4949
private static final String URI_PARAMETER = "uri";
@@ -67,6 +67,12 @@ public class BetterPlayerPlugin implements FlutterPlugin, ActivityAware, MethodC
6767
private static final String LICENSE_URL_PARAMETER = "licenseUrl";
6868
private static final String DRM_HEADERS_PARAMETER = "drmHeaders";
6969
private static final String MIX_WITH_OTHERS_PARAMETER = "mixWithOthers";
70+
public static final String URL_PARAMETER = "url";
71+
public static final String PRE_CACHE_SIZE_PARAMETER = "preCacheSize";
72+
public static final String MAX_CACHE_SIZE_PARAMETER = "maxCacheSize";
73+
public static final String MAX_CACHE_FILE_SIZE_PARAMETER = "maxCacheFileSize";
74+
public static final String HEADER_PARAMETER = "header_";
75+
7076

7177
private static final String INIT_METHOD = "init";
7278
private static final String CREATE_METHOD = "create";
@@ -87,6 +93,8 @@ public class BetterPlayerPlugin implements FlutterPlugin, ActivityAware, MethodC
8793
private static final String SET_MIX_WITH_OTHERS_METHOD = "setMixWithOthers";
8894
private static final String CLEAR_CACHE_METHOD = "clearCache";
8995
private static final String DISPOSE_METHOD = "dispose";
96+
private static final String PRE_CACHE_METHOD = "preCache";
97+
private static final String STOP_PRE_CACHE_METHOD = "stopPreCache";
9098

9199
private final LongSparseArray<BetterPlayer> videoPlayers = new LongSparseArray<>();
92100
private final LongSparseArray<Map<String, Object>> dataSources = new LongSparseArray<>();
@@ -102,6 +110,7 @@ public class BetterPlayerPlugin implements FlutterPlugin, ActivityAware, MethodC
102110
public BetterPlayerPlugin() {
103111
}
104112

113+
@SuppressWarnings("deprecation")
105114
private BetterPlayerPlugin(Registrar registrar) {
106115
this.flutterState =
107116
new FlutterState(
@@ -116,6 +125,7 @@ private BetterPlayerPlugin(Registrar registrar) {
116125
/**
117126
* Registers this with the stable v1 embedding. Will not respond to lifecycle events.
118127
*/
128+
@SuppressWarnings("deprecation")
119129
public static void registerWith(Registrar registrar) {
120130
final BetterPlayerPlugin plugin = new BetterPlayerPlugin(registrar);
121131

@@ -127,6 +137,7 @@ public static void registerWith(Registrar registrar) {
127137

128138
}
129139

140+
@SuppressWarnings("deprecation")
130141
@Override
131142
public void onAttachedToEngine(FlutterPluginBinding binding) {
132143
this.flutterState =
@@ -193,6 +204,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
193204
videoPlayers.put(handle.id(), player);
194205
break;
195206
}
207+
case PRE_CACHE_METHOD:
208+
preCache(call, result);
209+
break;
210+
case STOP_PRE_CACHE_METHOD:
211+
stopPreCache(call, result);
212+
break;
213+
case CLEAR_CACHE_METHOD:
214+
clearCache(result);
215+
break;
196216
default: {
197217
long textureId = ((Number) call.argument(TEXTURE_ID_PARAMETER)).longValue();
198218
BetterPlayer player = videoPlayers.get(textureId);
@@ -277,9 +297,6 @@ private void onMethodCall(MethodCall call, Result result, long textureId, Better
277297
case SET_MIX_WITH_OTHERS_METHOD:
278298
player.setMixWithOthers(call.argument(MIX_WITH_OTHERS_PARAMETER));
279299
break;
280-
case CLEAR_CACHE_METHOD:
281-
player.clearCache(flutterState.applicationContext);
282-
break;
283300
case DISPOSE_METHOD:
284301
dispose(player, textureId);
285302
result.success(null);
@@ -351,6 +368,50 @@ private void setDataSource(MethodCall call, Result result, BetterPlayer player)
351368
}
352369
}
353370

371+
/**
372+
* Start pre cache of video.
373+
*
374+
* @param call - invoked method data
375+
* @param result - result which should be updated
376+
*/
377+
private void preCache(MethodCall call, Result result) {
378+
Map<String, Object> dataSource = call.argument(DATA_SOURCE_PARAMETER);
379+
if (dataSource != null) {
380+
Number maxCacheSizeNumber = getParameter(dataSource, MAX_CACHE_SIZE_PARAMETER, 100 * 1024 * 1024);
381+
Number maxCacheFileSizeNumber = getParameter(dataSource, MAX_CACHE_FILE_SIZE_PARAMETER, 10 * 1024 * 1024);
382+
long maxCacheSize = maxCacheSizeNumber.longValue();
383+
long maxCacheFileSize = maxCacheFileSizeNumber.longValue();
384+
Number preCacheSizeNumber = getParameter(dataSource, PRE_CACHE_SIZE_PARAMETER, 3 * 1024 * 1024);
385+
long preCacheSize = preCacheSizeNumber.longValue();
386+
String uri = getParameter(dataSource, URI_PARAMETER, "");
387+
Map<String, String> headers = getParameter(dataSource, HEADERS_PARAMETER, new HashMap<>());
388+
389+
BetterPlayer.preCache(flutterState.applicationContext,
390+
uri,
391+
preCacheSize,
392+
maxCacheSize,
393+
maxCacheFileSize,
394+
headers,
395+
result
396+
);
397+
}
398+
}
399+
400+
/**
401+
* Stop pre cache video process (if exists).
402+
*
403+
* @param call - invoked method data
404+
* @param result - result which should be updated
405+
*/
406+
private void stopPreCache(MethodCall call, Result result) {
407+
String url = call.argument(URL_PARAMETER);
408+
BetterPlayer.stopPreCache(flutterState.applicationContext, url, result);
409+
}
410+
411+
private void clearCache(Result result) {
412+
BetterPlayer.clearCache(flutterState.applicationContext, result);
413+
}
414+
354415
private Long getTextureId(BetterPlayer betterPlayer) {
355416
for (int index = 0; index < videoPlayers.size(); index++) {
356417
if (betterPlayer == videoPlayers.valueAt(index)) {

android/src/main/java/com/jhomlala/better_player/CacheDataSourceFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class CacheDataSourceFactory implements DataSource.Factory {
3535

3636
@SuppressWarnings("NullableProblems")
3737
@Override
38-
public DataSource createDataSource() {
38+
public CacheDataSource createDataSource() {
3939
SimpleCache betterPlayerCache = BetterPlayerCache.createCache(context, maxCacheSize);
4040
return new CacheDataSource(
4141
betterPlayerCache,

0 commit comments

Comments
 (0)