Skip to content

Commit

Permalink
Feature/june changes (jhomlala#525)
Browse files Browse the repository at this point in the history
* Fixed cache clear on Android

* Added file check for file data source.

* Fix for black screen issue on iOS

* Updated changelog

* fix: iOS crashes encountered mainly when playing lots of video in HLS format. I definitely encounter a crash line 380 (jhomlala#513)

* Updated changelog

* Added key parameter in BetterPlayerCacheConfiguration to provide way to re-use same video between app session

* Format, updated pubspec.yaml

Co-authored-by: themadmrj <themadmrj@users.noreply.github.com>
Co-authored-by: Alexandre Roux <alex@tekartik.com>
  • Loading branch information
3 people authored Jun 3, 2021
1 parent c6f0b48 commit 5be8878
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 81 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.0.69
* Fixed cache clear on Android.
* Added file check for file data source.
* Fixed issue with black screen for some videos on iOS (by https://github.com/themadmrj)
* Fixed iOS eventSink issues. (by https://github.com/alextekartik)
* Added key parameter in BetterPlayerCacheConfiguration to provide way to re-use same video between app sessions.

## 0.0.68
* Added support for segmented subtitles.
* Added new fields in in BetterPlayerSubtitlesSource: `asmsIsSegmented`, `asmsSegmentsTime` and ` asmsSegments`. These fields shouldn't be configured
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ This plugin is based on [Chewie](https://github.com/brianegan/chewie). Chewie is

```yaml
dependencies:
better_player: ^0.0.68
better_player: ^0.0.69
```
2. Install it
Expand Down Expand Up @@ -737,6 +737,9 @@ Define cache configuration for given data source. Cache works only for network d
/// The maximum size of each individual file in bytes.
/// Android only option.
final int maxCacheFileSize;
///Cache key to re-use same cached data between app sessions.
final String? key;
```


Expand Down
50 changes: 38 additions & 12 deletions android/src/main/java/com/jhomlala/better_player/BetterPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ final class BetterPlayer {
void setDataSource(
Context context, String key, String dataSource, String formatHint, Result result,
Map<String, String> headers, boolean useCache, long maxCacheSize, long maxCacheFileSize,
long overriddenDuration, String licenseUrl, Map<String, String> drmHeaders) {
long overriddenDuration, String licenseUrl, Map<String, String> drmHeaders,
String cacheKey) {
this.key = key;
isInitialized = false;

Expand Down Expand Up @@ -186,7 +187,7 @@ void setDataSource(
dataSourceFactory = new DefaultDataSourceFactory(context, userAgent);
}

MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context);
MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, cacheKey, context);
if (overriddenDuration != 0) {
ClippingMediaSource clippingMediaSource = new ClippingMediaSource(mediaSource, 0, overriddenDuration * 1000);
exoPlayer.setMediaSource(clippingMediaSource);
Expand Down Expand Up @@ -451,7 +452,8 @@ public void disposeRemoteNotifications() {


private MediaSource buildMediaSource(
Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) {
Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, String cacheKey,
Context context) {
int type;
if (formatHint == null) {
String lastPathSegment = uri.getLastPathSegment();
Expand All @@ -478,28 +480,35 @@ private MediaSource buildMediaSource(
break;
}
}
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
mediaItemBuilder.setUri(uri);
if (cacheKey != null && cacheKey.length() > 0) {
mediaItemBuilder.setCustomCacheKey(cacheKey);
}
MediaItem mediaItem = mediaItemBuilder.build();
switch (type) {

case C.TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
.createMediaSource(mediaItem);
case C.TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
.createMediaSource(mediaItem);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
.createMediaSource(mediaItem);
case C.TYPE_OTHER:
return new ProgressiveMediaSource.Factory(mediaDataSourceFactory,
new DefaultExtractorsFactory())
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
.createMediaSource(mediaItem);
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
Expand Down Expand Up @@ -813,26 +822,43 @@ public void setMixWithOthers(Boolean mixWithOthers) {
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void clearCache(Context context, Result result) {
try {
File file = context.getCacheDir();
if (file != null) {
file.delete();
}
File file = new File(context.getCacheDir(), "betterPlayerCache");
deleteDirectory(file);
result.success(null);
} catch (Exception exception) {
Log.e(TAG, exception.toString());
result.error("", "", "");
}
}

private static void deleteDirectory(File file) {
if (file.isDirectory()) {
File[] entries = file.listFiles();
if (entries != null) {
for (File entry : entries) {
deleteDirectory(entry);
}
}
}
if (!file.delete()) {
Log.e(TAG, "Failed to delete cache dir.");
}
}


//Start pre cache of video. Invoke work manager job and start caching in background.
static void preCache(Context context, String dataSource, long preCacheSize,
long maxCacheSize, long maxCacheFileSize, Map<String, String> headers,
Result result) {
String cacheKey, Result result) {
Data.Builder dataBuilder = new Data.Builder()
.putString(BetterPlayerPlugin.URL_PARAMETER, dataSource)
.putLong(BetterPlayerPlugin.PRE_CACHE_SIZE_PARAMETER, preCacheSize)
.putLong(BetterPlayerPlugin.MAX_CACHE_SIZE_PARAMETER, maxCacheSize)
.putLong(BetterPlayerPlugin.MAX_CACHE_FILE_SIZE_PARAMETER, maxCacheFileSize);

if (cacheKey != null) {
dataBuilder.putString(BetterPlayerPlugin.CACHE_KEY_PARAMETER, cacheKey);
}
for (String headerKey : headers.keySet()) {
dataBuilder.putString(BetterPlayerPlugin.HEADER_PARAMETER + headerKey, headers.get(headerKey));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class BetterPlayerPlugin implements FlutterPlugin, ActivityAware, MethodC
public static final String HEADER_PARAMETER = "header_";
public static final String FILE_PATH_PARAMETER = "filePath";
public static final String ACTIVITY_NAME_PARAMETER = "activityName";
public static final String CACHE_KEY_PARAMETER = "cacheKey";


private static final String INIT_METHOD = "init";
Expand Down Expand Up @@ -300,7 +301,7 @@ private void setDataSource(MethodCall call, Result result, BetterPlayer player)
0L,
overriddenDuration.longValue(),
null,
null
null, null
);
} else {
boolean useCache = getParameter(dataSource, USE_CACHE_PARAMETER, false);
Expand All @@ -309,6 +310,7 @@ private void setDataSource(MethodCall call, Result result, BetterPlayer player)
long maxCacheSize = maxCacheSizeNumber.longValue();
long maxCacheFileSize = maxCacheFileSizeNumber.longValue();
String uri = getParameter(dataSource, URI_PARAMETER, "");
String cacheKey = getParameter(dataSource, CACHE_KEY_PARAMETER, null);
String formatHint = getParameter(dataSource, FORMAT_HINT_PARAMETER, null);
String licenseUrl = getParameter(dataSource, LICENSE_URL_PARAMETER, null);
Map<String, String> drmHeaders = getParameter(dataSource, DRM_HEADERS_PARAMETER, new HashMap<>());
Expand All @@ -324,7 +326,8 @@ private void setDataSource(MethodCall call, Result result, BetterPlayer player)
maxCacheFileSize,
overriddenDuration.longValue(),
licenseUrl,
drmHeaders
drmHeaders,
cacheKey
);
}
}
Expand All @@ -345,6 +348,7 @@ private void preCache(MethodCall call, Result result) {
Number preCacheSizeNumber = getParameter(dataSource, PRE_CACHE_SIZE_PARAMETER, 3 * 1024 * 1024);
long preCacheSize = preCacheSizeNumber.longValue();
String uri = getParameter(dataSource, URI_PARAMETER, "");
String cacheKey = getParameter(dataSource, CACHE_KEY_PARAMETER, null);
Map<String, String> headers = getParameter(dataSource, HEADERS_PARAMETER, new HashMap<>());

BetterPlayer.preCache(flutterState.applicationContext,
Expand All @@ -353,6 +357,7 @@ private void preCache(MethodCall call, Result result) {
maxCacheSize,
maxCacheFileSize,
headers,
cacheKey,
result
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public Result doWork() {
try {
Data data = getInputData();
String url = data.getString(BetterPlayerPlugin.URL_PARAMETER);
String cacheKey = data.getString(BetterPlayerPlugin.CACHE_KEY_PARAMETER);
long preCacheSize = data.getLong(BetterPlayerPlugin.PRE_CACHE_SIZE_PARAMETER, 0);
long maxCacheSize = data.getLong(BetterPlayerPlugin.MAX_CACHE_SIZE_PARAMETER, 0);
long maxCacheFileSize = data.getLong(BetterPlayerPlugin.MAX_CACHE_FILE_SIZE_PARAMETER, 0);
Expand All @@ -57,7 +58,12 @@ public Result doWork() {
if (DataSourceUtils.isHTTP(uri)) {
String userAgent = DataSourceUtils.getUserAgent(headers);
DataSource.Factory dataSourceFactory = DataSourceUtils.getDataSourceFactory(userAgent, headers);

DataSpec dataSpec = new DataSpec(uri, 0, preCacheSize);
if (cacheKey != null && cacheKey.length() > 0) {
dataSpec = dataSpec.buildUpon().setKey(cacheKey).build();
}

CacheDataSourceFactory cacheDataSourceFactory =
new CacheDataSourceFactory(mContext, maxCacheSize, maxCacheFileSize, dataSourceFactory);

Expand Down
12 changes: 8 additions & 4 deletions example/lib/pages/cache_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ class _CachePageState extends State<CachePage> {
BetterPlayerDataSourceType.network,
Constants.elephantDreamVideoUrl,
cacheConfiguration: BetterPlayerCacheConfiguration(
useCache: true,
preCacheSize: 10 * 1024 * 1024,
maxCacheSize: 10 * 1024 * 1024,
maxCacheFileSize: 10 * 1024 * 1024),
useCache: true,
preCacheSize: 10 * 1024 * 1024,
maxCacheSize: 10 * 1024 * 1024,
maxCacheFileSize: 10 * 1024 * 1024,

///Android only option to use cached video between app sessions
key: "testCacheKey",
),
);
_betterPlayerController = BetterPlayerController(betterPlayerConfiguration);
super.initState();
Expand Down
50 changes: 27 additions & 23 deletions ios/Classes/FLTBetterPlayerPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -272,26 +272,24 @@ - (void)addVideoOutput {
}

- (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack {
CGAffineTransform transform = videoTrack.preferredTransform;
// TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect?
// At least 2 user videos show a black screen when in portrait mode if we directly use the
// videoTrack.preferredTransform Setting tx to the height of the video instead of 0, properly
// displays the video https://github.com/flutter/flutter/issues/17606#issuecomment-413473181
if (transform.tx == 0 && transform.ty == 0) {
NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a)));
NSLog(@"TX and TY are 0. Rotation: %ld. Natural width,height: %f, %f", (long)rotationDegrees,
videoTrack.naturalSize.width, videoTrack.naturalSize.height);
if (rotationDegrees == 90) {
NSLog(@"Setting transform tx");
transform.tx = videoTrack.naturalSize.height;
transform.ty = 0;
} else if (rotationDegrees == 270) {
NSLog(@"Setting transform ty");
transform.tx = 0;
transform.ty = videoTrack.naturalSize.width;
}
}
return transform;
CGAffineTransform transform = videoTrack.preferredTransform;
// TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect?
// At least 2 user videos show a black screen when in portrait mode if we directly use the
// videoTrack.preferredTransform Setting tx to the height of the video instead of 0, properly
// displays the video https://github.com/flutter/flutter/issues/17606#issuecomment-413473181
NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a)));
NSLog(@"VIDEO__ %f, %f, %f, %f, %li", transform.tx, transform.ty, videoTrack.naturalSize.height, videoTrack.naturalSize.width, (long)rotationDegrees);
if (rotationDegrees == 90) {
transform.tx = videoTrack.naturalSize.height;
transform.ty = 0;
} else if (rotationDegrees == 180) {
transform.tx = videoTrack.naturalSize.width;
transform.ty = videoTrack.naturalSize.height;
} else if (rotationDegrees == 270) {
transform.tx = 0;
transform.ty = videoTrack.naturalSize.width;
}
return transform;
}

- (void)setDataSourceAsset:(NSString*)asset withKey:(NSString*)key overriddenDuration:(int) overriddenDuration{
Expand Down Expand Up @@ -376,10 +374,12 @@ -(void)startStalledCheck{
} else {
_stalledCount++;
if (_stalledCount > 60){
_eventSink([FlutterError
if (_eventSink != nil) {
_eventSink([FlutterError
errorWithCode:@"VideoError"
message:@"Failed to load video: playback stalled"
details:nil]);
}
return;
}
[self performSelector:@selector(startStalledCheck) withObject:nil afterDelay:1];
Expand Down Expand Up @@ -416,13 +416,17 @@ - (void)observeValueForKeyPath:(NSString*)path

if (_player.timeControlStatus == AVPlayerTimeControlStatusPaused){
_lastAvPlayerTimeControlStatus = _player.timeControlStatus;
_eventSink(@{@"event" : @"pause"});
if (_eventSink != nil) {
_eventSink(@{@"event" : @"pause"});
}
return;

}
if (_player.timeControlStatus == AVPlayerTimeControlStatusPlaying){
_lastAvPlayerTimeControlStatus = _player.timeControlStatus;
_eventSink(@{@"event" : @"play"});
if (_eventSink != nil) {
_eventSink(@{@"event" : @"play"});
}
}
}
}
Expand Down
17 changes: 10 additions & 7 deletions lib/src/configuration/better_player_cache_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class BetterPlayerCacheConfiguration {
/// The maximum cache size to keep on disk in bytes. This value is used only
/// when first video access. cache. This value is used for all players within
/// your app. It can't be changed during app work.
// Android only option.
/// Android only option.
final int maxCacheSize;

/// The maximum size of each individual file in bytes.
Expand All @@ -19,10 +19,13 @@ class BetterPlayerCacheConfiguration {
/// The size to download.
final int preCacheSize;

const BetterPlayerCacheConfiguration({
this.useCache = false,
this.maxCacheSize = 10 * 1024 * 1024,
this.maxCacheFileSize = 10 * 1024 * 1024,
this.preCacheSize = 3 * 1024 * 1024,
});
///Cache key to re-use same cached data between app sessions.
final String? key;

const BetterPlayerCacheConfiguration(
{this.useCache = false,
this.maxCacheSize = 10 * 1024 * 1024,
this.maxCacheFileSize = 10 * 1024 * 1024,
this.preCacheSize = 3 * 1024 * 1024,
this.key});
}
Loading

0 comments on commit 5be8878

Please sign in to comment.