diff --git a/AUTHORS b/AUTHORS index 51345c9a3481..a747fa46aba3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -59,3 +59,5 @@ Kazuki Yamaguchi Eitan Schwartz Chris Rutkowski Juan Alvarez +Aleksandr Yurkovskiy +Iurii Dorofeev diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 01601c7cc44c..9fdaccb5430b 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.0 + +* Add caching functionality to videos from network sources. + ## 0.11.1+2 * Update android compileSdkVersion to 29. @@ -85,7 +89,6 @@ * Added support for cleaning up the plugin if used for add-to-app (Flutter v1.15.3 is required for that feature). - ## 0.10.7 * `VideoPlayerController` support for reading closed caption files. diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java index 78da7150edf0..7655d810ad74 100644 --- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java +++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java @@ -12,6 +12,53 @@ @SuppressWarnings("unused") public class Messages { + /** Generated class from Pigeon that represents data sent in messages. */ + public static class InitializeMessage { + private Long maxCacheSize; + + public Long getMaxCacheSize() { + return maxCacheSize; + } + + public void setMaxCacheSize(Long setterArg) { + this.maxCacheSize = setterArg; + } + + private Long maxCacheFileSize; + + public Long getMaxCacheFileSize() { + return maxCacheFileSize; + } + + public void setMaxCacheFileSize(Long setterArg) { + this.maxCacheFileSize = setterArg; + } + + HashMap toMap() { + HashMap toMapResult = new HashMap<>(); + toMapResult.put("maxCacheSize", maxCacheSize); + toMapResult.put("maxCacheFileSize", maxCacheFileSize); + return toMapResult; + } + + static InitializeMessage fromMap(HashMap map) { + InitializeMessage fromMapResult = new InitializeMessage(); + Object maxCacheSize = map.get("maxCacheSize"); + fromMapResult.maxCacheSize = + (maxCacheSize == null) + ? null + : ((maxCacheSize instanceof Integer) ? (Integer) maxCacheSize : (Long) maxCacheSize); + Object maxCacheFileSize = map.get("maxCacheFileSize"); + fromMapResult.maxCacheFileSize = + (maxCacheFileSize == null) + ? null + : ((maxCacheFileSize instanceof Integer) + ? (Integer) maxCacheFileSize + : (Long) maxCacheFileSize); + return fromMapResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static class TextureMessage { private Long textureId; @@ -83,12 +130,23 @@ public void setFormatHint(String setterArg) { this.formatHint = setterArg; } + private Boolean useCache; + + public Boolean getUseCache() { + return useCache; + } + + public void setUseCache(Boolean setterArg) { + this.useCache = setterArg; + } + HashMap toMap() { HashMap toMapResult = new HashMap<>(); toMapResult.put("asset", asset); toMapResult.put("uri", uri); toMapResult.put("packageName", packageName); toMapResult.put("formatHint", formatHint); + toMapResult.put("useCache", useCache); return toMapResult; } @@ -102,6 +160,8 @@ static CreateMessage fromMap(HashMap map) { fromMapResult.packageName = (String) packageName; Object formatHint = map.get("formatHint"); fromMapResult.formatHint = (String) formatHint; + Object useCache = map.get("useCache"); + fromMapResult.useCache = (Boolean) useCache; return fromMapResult; } } @@ -305,7 +365,7 @@ static MixWithOthersMessage fromMap(HashMap map) { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface VideoPlayerApi { - void initialize(); + void initialize(InitializeMessage arg); TextureMessage create(CreateMessage arg); @@ -340,7 +400,9 @@ static void setup(BinaryMessenger binaryMessenger, VideoPlayerApi api) { (message, reply) -> { HashMap wrapped = new HashMap<>(); try { - api.initialize(); + @SuppressWarnings("ConstantConditions") + InitializeMessage input = InitializeMessage.fromMap((HashMap) message); + api.initialize(input); wrapped.put("result", null); } catch (Exception exception) { wrapped.put("error", wrapError(exception)); diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 8f8c898dea27..4c7be4d1495c 100644 --- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -27,12 +27,19 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.FileDataSource; +import com.google.android.exoplayer2.upstream.cache.CacheDataSink; +import com.google.android.exoplayer2.upstream.cache.CacheDataSource; +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; +import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import io.flutter.plugin.common.EventChannel; import io.flutter.view.TextureRegistry; +import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -65,7 +72,10 @@ final class VideoPlayer { TextureRegistry.SurfaceTextureEntry textureEntry, String dataSource, String formatHint, - VideoPlayerOptions options) { + VideoPlayerOptions options, + long maxCacheSize, + long maxCacheFileSize, + boolean useCache) { this.eventChannel = eventChannel; this.textureEntry = textureEntry; this.options = options; @@ -84,6 +94,10 @@ final class VideoPlayer { DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, true); + if (useCache && maxCacheSize > 0 && maxCacheFileSize > 0) { + dataSourceFactory = + new CacheDataSourceFactory(context, maxCacheSize, maxCacheFileSize, dataSourceFactory); + } } else { dataSourceFactory = new DefaultDataSourceFactory(context, "ExoPlayer"); } @@ -287,4 +301,42 @@ void dispose() { exoPlayer.release(); } } + + static class CacheDataSourceFactory implements DataSource.Factory { + private final Context context; + private final DefaultDataSourceFactory defaultDatasourceFactory; + private final long maxFileSize, maxCacheSize; + private static SimpleCache downloadCache; + + CacheDataSourceFactory( + Context context, + long maxCacheSize, + long maxFileSize, + DataSource.Factory upstreamDataSource) { + super(); + this.context = context; + this.maxCacheSize = maxCacheSize; + this.maxFileSize = maxFileSize; + DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + defaultDatasourceFactory = + new DefaultDataSourceFactory(this.context, bandwidthMeter, upstreamDataSource); + } + + @Override + public DataSource createDataSource() { + LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSize); + + if (downloadCache == null) { + downloadCache = new SimpleCache(new File(context.getCacheDir(), "video"), evictor); + } + + return new CacheDataSource( + downloadCache, + defaultDatasourceFactory.createDataSource(), + new FileDataSource(), + new CacheDataSink(downloadCache, maxFileSize), + CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, + null); + } + } } diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 1beb79c4295d..a3d8059473ae 100644 --- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -13,6 +13,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugins.videoplayer.Messages.CreateMessage; +import io.flutter.plugins.videoplayer.Messages.InitializeMessage; import io.flutter.plugins.videoplayer.Messages.LoopingMessage; import io.flutter.plugins.videoplayer.Messages.MixWithOthersMessage; import io.flutter.plugins.videoplayer.Messages.PlaybackSpeedMessage; @@ -32,6 +33,9 @@ public class VideoPlayerPlugin implements FlutterPlugin, VideoPlayerApi { private FlutterState flutterState; private VideoPlayerOptions options = new VideoPlayerOptions(); + private long maxCacheSize; + private long maxCacheFileSize; + /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ public VideoPlayerPlugin() {} @@ -110,8 +114,10 @@ private void onDestroy() { disposeAllPlayers(); } - public void initialize() { + public void initialize(InitializeMessage arg) { disposeAllPlayers(); + maxCacheSize = arg.getMaxCacheSize(); + maxCacheFileSize = arg.getMaxCacheFileSize(); } public TextureMessage create(CreateMessage arg) { @@ -137,7 +143,10 @@ public TextureMessage create(CreateMessage arg) { handle, "asset:///" + assetLookupKey, null, - options); + options, + maxCacheSize, + maxCacheFileSize, + false); videoPlayers.put(handle.id(), player); } else { player = @@ -147,7 +156,10 @@ public TextureMessage create(CreateMessage arg) { handle, arg.getUri(), arg.getFormatHint(), - options); + options, + maxCacheSize, + maxCacheFileSize, + arg.getUseCache()); videoPlayers.put(handle.id(), player); } diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index a99b9da6bd0c..2045ed5a3b17 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -6,7 +6,6 @@ /// An example of using the plugin, controlling lifecycle and playback of the /// video. - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; @@ -221,6 +220,7 @@ class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + useCache: true, ); _controller.addListener(() { diff --git a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m index e6a4f6ccb0b7..9045f3805b84 100644 --- a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m +++ b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m @@ -5,6 +5,7 @@ #import "FLTVideoPlayerPlugin.h" #import #import +#import "VIMediaCache.h" #import "messages.h" #if !__has_feature(objc_arc) @@ -51,6 +52,7 @@ - (void)play; - (void)pause; - (void)setIsLooping:(bool)isLooping; - (void)updatePlayingState; ++ (VIResourceLoaderManager*)resourceLoaderManager; @end static void* timeRangeContext = &timeRangeContext; @@ -163,10 +165,29 @@ - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { } - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { - AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; + return [self initWithURL:url frameUpdater:frameUpdater enableCache:NO]; +} + +- (instancetype)initWithURL:(NSURL*)url + frameUpdater:(FLTFrameUpdater*)frameUpdater + enableCache:(BOOL)enableCache { + AVPlayerItem* item; + if (enableCache) { + item = [[FLTVideoPlayer resourceLoaderManager] playerItemWithURL:url]; + } else { + item = [AVPlayerItem playerItemWithURL:url]; + } return [self initWithPlayerItem:item frameUpdater:frameUpdater]; } ++ (VIResourceLoaderManager*)resourceLoaderManager { + static VIResourceLoaderManager* resourceLoaderManager = nil; + if (resourceLoaderManager == nil) { + resourceLoaderManager = [VIResourceLoaderManager new]; + } + return resourceLoaderManager; +} + - (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack { CGAffineTransform transform = videoTrack.preferredTransform; // TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect? @@ -450,6 +471,8 @@ @interface FLTVideoPlayerPlugin () @property(readonly, weak, nonatomic) NSObject* messenger; @property(readonly, strong, nonatomic) NSMutableDictionary* players; @property(readonly, strong, nonatomic) NSObject* registrar; +@property(readonly, nonatomic) long maxCacheSize; +@property(readonly, nonatomic) long maxCacheFileSize; @end @implementation FLTVideoPlayerPlugin @@ -497,7 +520,7 @@ - (FLTTextureMessage*)onPlayerSetup:(FLTVideoPlayer*)player return result; } -- (void)initialize:(FlutterError* __autoreleasing*)error { +- (void)initialize:(FLTInitializeMessage*)input error:(FlutterError**)error { // Allow audio playback when the Ring/Silent switch is set to silent [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; @@ -506,6 +529,8 @@ - (void)initialize:(FlutterError* __autoreleasing*)error { [_players[textureId] dispose]; } [_players removeAllObjects]; + _maxCacheSize = [input.maxCacheSize longValue]; + _maxCacheFileSize = [input.maxCacheFileSize longValue]; } - (FLTTextureMessage*)create:(FLTCreateMessage*)input error:(FlutterError**)error { @@ -521,8 +546,20 @@ - (FLTTextureMessage*)create:(FLTCreateMessage*)input error:(FlutterError**)erro player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; return [self onPlayerSetup:player frameUpdater:frameUpdater]; } else if (input.uri) { - player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:input.uri] - frameUpdater:frameUpdater]; + BOOL useCache = input.useCache; + BOOL enableCache = _maxCacheSize > 0 && _maxCacheFileSize > 0 && useCache; + if (enableCache) { + NSString* escapedURL = [input.uri + stringByAddingPercentEncodingWithAllowedCharacters:NSMutableCharacterSet + .alphanumericCharacterSet]; + + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:escapedURL] + frameUpdater:frameUpdater + enableCache:enableCache]; + } else { + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:input.uri] + frameUpdater:frameUpdater]; + } return [self onPlayerSetup:player frameUpdater:frameUpdater]; } else { *error = [FlutterError errorWithCode:@"video_player" message:@"not implemented" details:nil]; diff --git a/packages/video_player/video_player/ios/Classes/messages.h b/packages/video_player/video_player/ios/Classes/messages.h index 3c68b3dd24d4..14ebfd38b44e 100644 --- a/packages/video_player/video_player/ios/Classes/messages.h +++ b/packages/video_player/video_player/ios/Classes/messages.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN +@class FLTInitializeMessage; @class FLTTextureMessage; @class FLTCreateMessage; @class FLTLoopingMessage; @@ -15,6 +16,11 @@ NS_ASSUME_NONNULL_BEGIN @class FLTPositionMessage; @class FLTMixWithOthersMessage; +@interface FLTInitializeMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *maxCacheSize; +@property(nonatomic, strong, nullable) NSNumber *maxCacheFileSize; +@end + @interface FLTTextureMessage : NSObject @property(nonatomic, strong, nullable) NSNumber *textureId; @end @@ -24,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, nullable) NSString *uri; @property(nonatomic, copy, nullable) NSString *packageName; @property(nonatomic, copy, nullable) NSString *formatHint; +@property(nonatomic, strong, nullable) NSNumber *useCache; @end @interface FLTLoopingMessage : NSObject @@ -51,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN @end @protocol FLTVideoPlayerApi -- (void)initialize:(FlutterError *_Nullable *_Nonnull)error; +- (void)initialize:(FLTInitializeMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; - (nullable FLTTextureMessage *)create:(FLTCreateMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; - (void)dispose:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; diff --git a/packages/video_player/video_player/ios/Classes/messages.m b/packages/video_player/video_player/ios/Classes/messages.m index e71f8b89254d..022998b6b05c 100644 --- a/packages/video_player/video_player/ios/Classes/messages.m +++ b/packages/video_player/video_player/ios/Classes/messages.m @@ -20,6 +20,10 @@ errorDict, @"error", nil]; } +@interface FLTInitializeMessage () ++ (FLTInitializeMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end @interface FLTTextureMessage () + (FLTTextureMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @@ -49,6 +53,28 @@ + (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end +@implementation FLTInitializeMessage ++ (FLTInitializeMessage *)fromMap:(NSDictionary *)dict { + FLTInitializeMessage *result = [[FLTInitializeMessage alloc] init]; + result.maxCacheSize = dict[@"maxCacheSize"]; + if ((NSNull *)result.maxCacheSize == [NSNull null]) { + result.maxCacheSize = nil; + } + result.maxCacheFileSize = dict[@"maxCacheFileSize"]; + if ((NSNull *)result.maxCacheFileSize == [NSNull null]) { + result.maxCacheFileSize = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.maxCacheSize ? self.maxCacheSize : [NSNull null]), + @"maxCacheSize", + (self.maxCacheFileSize ? self.maxCacheFileSize : [NSNull null]), + @"maxCacheFileSize", nil]; +} +@end + @implementation FLTTextureMessage + (FLTTextureMessage *)fromMap:(NSDictionary *)dict { FLTTextureMessage *result = [[FLTTextureMessage alloc] init]; @@ -59,9 +85,9 @@ + (FLTTextureMessage *)fromMap:(NSDictionary *)dict { return result; } - (NSDictionary *)toMap { - return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", nil]; + return + [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), + @"textureId", nil]; } @end @@ -84,6 +110,10 @@ + (FLTCreateMessage *)fromMap:(NSDictionary *)dict { if ((NSNull *)result.formatHint == [NSNull null]) { result.formatHint = nil; } + result.useCache = dict[@"useCache"]; + if ((NSNull *)result.useCache == [NSNull null]) { + result.useCache = nil; + } return result; } - (NSDictionary *)toMap { @@ -93,7 +123,8 @@ - (NSDictionary *)toMap { (self.packageName ? self.packageName : [NSNull null]), @"packageName", (self.formatHint ? self.formatHint : [NSNull null]), - @"formatHint", nil]; + @"formatHint", (self.useCache ? self.useCache : [NSNull null]), + @"useCache", nil]; } @end @@ -112,10 +143,9 @@ + (FLTLoopingMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", - (self.isLooping != nil ? self.isLooping : [NSNull null]), - @"isLooping", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.isLooping ? self.isLooping : [NSNull null]), @"isLooping", + nil]; } @end @@ -134,9 +164,8 @@ + (FLTVolumeMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", (self.volume != nil ? self.volume : [NSNull null]), - @"volume", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.volume ? self.volume : [NSNull null]), @"volume", nil]; } @end @@ -155,9 +184,8 @@ + (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", (self.speed != nil ? self.speed : [NSNull null]), - @"speed", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.speed ? self.speed : [NSNull null]), @"speed", nil]; } @end @@ -176,10 +204,9 @@ + (FLTPositionMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", - (self.position != nil ? self.position : [NSNull null]), - @"position", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.position ? self.position : [NSNull null]), @"position", + nil]; } @end @@ -194,7 +221,7 @@ + (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.mixWithOthers != nil ? self.mixWithOthers : [NSNull null]), + dictionaryWithObjectsAndKeys:(self.mixWithOthers ? self.mixWithOthers : [NSNull null]), @"mixWithOthers", nil]; } @end @@ -207,7 +234,8 @@ void FLTVideoPlayerApiSetup(id binaryMessenger, id 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index ac1645085e36..cac14d4a1859 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -11,17 +11,13 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'src/closed_caption_file.dart'; + export 'package:video_player_platform_interface/video_player_platform_interface.dart' show DurationRange, DataSourceType, VideoFormat, VideoPlayerOptions; -import 'src/closed_caption_file.dart'; export 'src/closed_caption_file.dart'; -final VideoPlayerPlatform _videoPlayerPlatform = VideoPlayerPlatform.instance - // This will clear all open videos on the platform when a full restart is - // performed. - ..init(); - /// The duration, current position, buffering state, error state and settings /// of a [VideoPlayerController]. class VideoPlayerValue { @@ -178,6 +174,7 @@ class VideoPlayerController extends ValueNotifier { {this.package, this.closedCaptionFile, this.videoPlayerOptions}) : dataSourceType = DataSourceType.asset, formatHint = null, + useCache = null, super(VideoPlayerValue(duration: null)); /// Constructs a [VideoPlayerController] playing a video from obtained from @@ -186,25 +183,41 @@ class VideoPlayerController extends ValueNotifier { /// The URI for the video is given by the [dataSource] argument and must not be /// null. /// **Android only**: The [formatHint] option allows the caller to override - /// the video format detection code. - VideoPlayerController.network(this.dataSource, - {this.formatHint, this.closedCaptionFile, this.videoPlayerOptions}) - : dataSourceType = DataSourceType.network, + /// the video format detection code. The [useCache] argument must be non-null, + /// default is false. + VideoPlayerController.network( + this.dataSource, { + this.formatHint, + this.closedCaptionFile, + this.videoPlayerOptions, + bool useCache = false, + }) : assert(useCache != null), + dataSourceType = DataSourceType.network, package = null, + useCache = useCache, super(VideoPlayerValue(duration: null)); /// Constructs a [VideoPlayerController] playing a video from a file. /// /// This will load the file from the file-URI given by: /// `'file://${file.path}'`. - VideoPlayerController.file(File file, - {this.closedCaptionFile, this.videoPlayerOptions}) - : dataSource = 'file://${file.path}', + VideoPlayerController.file( + File file, { + this.closedCaptionFile, + this.videoPlayerOptions, + }) : dataSource = 'file://${file.path}', dataSourceType = DataSourceType.file, package = null, formatHint = null, + useCache = null, super(VideoPlayerValue(duration: null)); + /// The maximum cache size to keep on disk in bytes. + static int _maxCacheSize = 100 * 1024 * 1024; + + /// The maximum size of each individual file in bytes. + static int _maxCacheFileSize = 10 * 1024 * 1024; + int _textureId; /// The URI to the video file. This will be in different formats depending on @@ -222,6 +235,9 @@ class VideoPlayerController extends ValueNotifier { /// Provide additional configuration options (optional). Like setting the audio mode to mix final VideoPlayerOptions videoPlayerOptions; + /// Use cache for this data source or not. Used only for network data source. + final bool useCache; + /// Only set for [asset] videos. The package that the asset was loaded from. final String package; @@ -235,6 +251,7 @@ class VideoPlayerController extends ValueNotifier { ClosedCaptionFile _closedCaptionFile; Timer _timer; bool _isDisposed = false; + static Completer _pluginInitializingCompleter; Completer _creatingCompleter; StreamSubscription _eventSubscription; _VideoAppLifeCycleObserver _lifeCycleObserver; @@ -244,8 +261,30 @@ class VideoPlayerController extends ValueNotifier { @visibleForTesting int get textureId => _textureId; + /// Set the cache size in bytes. Default is maxSize of `100 * 1024 * 1024` + /// and maxFileSize of `10 * 1024 * 1024`. + /// + /// Cache used only for network data source. + /// + /// Throws StateError if you try to set the cache size twice. You can only set it once. + static void setCacheSize(int maxSize, int maxFileSize) { + assert(maxSize != null && maxSize > 0); + assert(maxFileSize != null && maxFileSize > 0); + + if (_pluginInitializingCompleter != null) { + throw StateError( + "You can only set the VideoPlayerController cache size once.", + ); + } + + _maxCacheSize = maxSize; + _maxCacheFileSize = maxFileSize; + } + /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { + await _ensureVideoPluginInitialized(); + _lifeCycleObserver = _VideoAppLifeCycleObserver(this); _lifeCycleObserver.initialize(); _creatingCompleter = Completer(); @@ -264,6 +303,7 @@ class VideoPlayerController extends ValueNotifier { sourceType: DataSourceType.network, uri: dataSource, formatHint: formatHint, + useCache: useCache, ); break; case DataSourceType.file: @@ -275,11 +315,12 @@ class VideoPlayerController extends ValueNotifier { } if (videoPlayerOptions?.mixWithOthers != null) { - await _videoPlayerPlatform + await VideoPlayerPlatform.instance .setMixWithOthers(videoPlayerOptions.mixWithOthers); } - _textureId = await _videoPlayerPlatform.create(dataSourceDescription); + _textureId = + await VideoPlayerPlatform.instance.create(dataSourceDescription); _creatingCompleter.complete(null); final Completer initializingCompleter = Completer(); @@ -333,12 +374,25 @@ class VideoPlayerController extends ValueNotifier { } } - _eventSubscription = _videoPlayerPlatform + _eventSubscription = VideoPlayerPlatform.instance .videoEventsFor(_textureId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } + Future _ensureVideoPluginInitialized() async { + if (_pluginInitializingCompleter != null) { + return _pluginInitializingCompleter.future; + } + + _pluginInitializingCompleter = Completer(); + + await VideoPlayerPlatform.instance.init(_maxCacheSize, _maxCacheFileSize); + _pluginInitializingCompleter.complete(null); + + return _pluginInitializingCompleter.future; + } + @override Future dispose() async { if (_creatingCompleter != null) { @@ -347,7 +401,7 @@ class VideoPlayerController extends ValueNotifier { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); - await _videoPlayerPlatform.dispose(_textureId); + await VideoPlayerPlatform.instance.dispose(_textureId); } _lifeCycleObserver.dispose(); } @@ -382,7 +436,7 @@ class VideoPlayerController extends ValueNotifier { if (!value.initialized || _isDisposed) { return; } - await _videoPlayerPlatform.setLooping(_textureId, value.isLooping); + await VideoPlayerPlatform.instance.setLooping(_textureId, value.isLooping); } Future _applyPlayPause() async { @@ -390,7 +444,7 @@ class VideoPlayerController extends ValueNotifier { return; } if (value.isPlaying) { - await _videoPlayerPlatform.play(_textureId); + await VideoPlayerPlatform.instance.play(_textureId); // Cancel previous timer. _timer?.cancel(); @@ -414,7 +468,7 @@ class VideoPlayerController extends ValueNotifier { await _applyPlaybackSpeed(); } else { _timer?.cancel(); - await _videoPlayerPlatform.pause(_textureId); + await VideoPlayerPlatform.instance.pause(_textureId); } } @@ -422,7 +476,7 @@ class VideoPlayerController extends ValueNotifier { if (!value.initialized || _isDisposed) { return; } - await _videoPlayerPlatform.setVolume(_textureId, value.volume); + await VideoPlayerPlatform.instance.setVolume(_textureId, value.volume); } Future _applyPlaybackSpeed() async { @@ -435,7 +489,7 @@ class VideoPlayerController extends ValueNotifier { // the video is manually played from Flutter. if (!value.isPlaying) return; - await _videoPlayerPlatform.setPlaybackSpeed( + await VideoPlayerPlatform.instance.setPlaybackSpeed( _textureId, value.playbackSpeed, ); @@ -446,7 +500,7 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposed) { return null; } - return await _videoPlayerPlatform.getPosition(_textureId); + return await VideoPlayerPlatform.instance.getPosition(_textureId); } /// Sets the video's current timestamp to be at [moment]. The next @@ -463,7 +517,7 @@ class VideoPlayerController extends ValueNotifier { } else if (position < const Duration()) { position = const Duration(); } - await _videoPlayerPlatform.seekTo(_textureId, position); + await VideoPlayerPlatform.instance.seekTo(_textureId, position); _updatePosition(position); } @@ -624,7 +678,7 @@ class _VideoPlayerState extends State { Widget build(BuildContext context) { return _textureId == null ? Container() - : _videoPlayerPlatform.buildView(_textureId); + : VideoPlayerPlatform.instance.buildView(_textureId); } } diff --git a/packages/video_player/video_player/pigeons/messages.dart b/packages/video_player/video_player/pigeons/messages.dart index 427aea279071..be61e0f524e3 100644 --- a/packages/video_player/video_player/pigeons/messages.dart +++ b/packages/video_player/video_player/pigeons/messages.dart @@ -1,5 +1,10 @@ import 'package:pigeon/pigeon_lib.dart'; +class InitializeMessage { + int maxCacheSize; + int maxCacheFileSize; +} + class TextureMessage { int textureId; } @@ -29,6 +34,7 @@ class CreateMessage { String uri; String packageName; String formatHint; + bool useCache; } class MixWithOthersMessage { @@ -37,7 +43,7 @@ class MixWithOthersMessage { @HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi') abstract class VideoPlayerApi { - void initialize(); + void initialize(InitializeMessage msg); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 96ced42cedcf..98c17e14928e 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -4,7 +4,7 @@ description: Flutter plugin for displaying inline video with other Flutter # 0.10.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.11.1+2 +version: 0.12.0 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: @@ -20,14 +20,23 @@ flutter: dependencies: meta: ^1.0.5 - video_player_platform_interface: ^2.2.0 - + video_player_platform_interface: #^2.3.0 + # workaround for success build checks in PR + git: + url: git://github.com/sanekyy/plugins.git + ref: caching + path: packages/video_player/video_player_platform_interface # The design on https://flutter.dev/go/federated-plugins was to leave # this constraint as "any". We cannot do it right now as it fails pub publish # validation, so we set a ^ constraint. # TODO(amirh): Revisit this (either update this part in the design or the pub tool). # https://github.com/flutter/flutter/issues/46264 - video_player_web: '>=0.1.4 <2.0.0' + video_player_web: #'>=0.1.5 <2.0.0' + # workaround for success build checks in PR + git: + url: git://github.com/sanekyy/plugins.git + ref: caching + path: packages/video_player/video_player_web flutter: sdk: flutter diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index a40781a1e9ce..1539f0e8e6ac 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -69,6 +69,9 @@ class FakeController extends ValueNotifier @override VideoPlayerOptions get videoPlayerOptions => null; + + @override + bool get useCache => false; } Future _loadClosedCaption() async => @@ -192,461 +195,510 @@ void main() { null); }); - test('network', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); + group('network', () { + test('with cache', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + useCache: true, + ); + await controller.initialize(); + + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + 'https://127.0.0.1'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + null); + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0].useCache, true); + }); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, - 'https://127.0.0.1'); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, null); - }); + test('without cache', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + useCache: false, + ); + await controller.initialize(); + + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + 'https://127.0.0.1'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + null); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].useCache, + false); + }); - test('network with hint', () async { - final VideoPlayerController controller = VideoPlayerController.network( + test('without cache by default', () async { + final VideoPlayerController controller = + VideoPlayerController.network( 'https://127.0.0.1', - formatHint: VideoFormat.dash); - await controller.initialize(); + ); + await controller.initialize(); + + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + 'https://127.0.0.1'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + null); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].useCache, + false); + }); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, - 'https://127.0.0.1'); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, - 'dash'); - }); + test('network with hint', () async { + final VideoPlayerController controller = + VideoPlayerController.network('https://127.0.0.1', + formatHint: VideoFormat.dash); + await controller.initialize(); + + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + 'https://127.0.0.1'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + 'dash'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].useCache, + false); + }); - test('init errors', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'http://testing.com/invalid_url', - ); - try { - dynamic error; - fakeVideoPlayerPlatform.forceInitError = true; - await controller.initialize().catchError((dynamic e) => error = e); - final PlatformException platformEx = error; - expect(platformEx.code, equals('VideoError')); - } finally { - fakeVideoPlayerPlatform.forceInitError = false; - } - }); + test('init errors', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'http://testing.com/invalid_url', + ); + try { + dynamic error; + fakeVideoPlayerPlatform.forceInitError = true; + await controller.initialize().catchError((dynamic e) => error = e); + final PlatformException platformEx = error; + expect(platformEx.code, equals('VideoError')); + } finally { + fakeVideoPlayerPlatform.forceInitError = false; + } + }); - test('file', () async { - final VideoPlayerController controller = - VideoPlayerController.file(File('a.avi')); - await controller.initialize(); + test('file', () async { + final VideoPlayerController controller = + VideoPlayerController.file(File('a.avi')); + await controller.initialize(); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, - 'file://a.avi'); + expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + 'file://a.avi'); + }); }); - }); - - test('dispose', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - expect(controller.textureId, isNull); - expect(await controller.position, const Duration(seconds: 0)); - await controller.initialize(); - - await controller.dispose(); - - expect(controller.textureId, isNotNull); - expect(await controller.position, isNull); - }); - - test('play', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.isPlaying, isFalse); - await controller.play(); - - expect(controller.value.isPlaying, isTrue); - - // The two last calls will be "play" and then "setPlaybackSpeed". The - // reason for this is that "play" calls "setPlaybackSpeed" internally. - expect( - fakeVideoPlayerPlatform - .calls[fakeVideoPlayerPlatform.calls.length - 2], - 'play'); - expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed'); - }); - - test('setLooping', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.isLooping, isFalse); - await controller.setLooping(true); - - expect(controller.value.isLooping, isTrue); - }); - test('pause', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - await controller.play(); - expect(controller.value.isPlaying, isTrue); - - await controller.pause(); - - expect(controller.value.isPlaying, isFalse); - expect(fakeVideoPlayerPlatform.calls.last, 'pause'); - }); - - group('seekTo', () { - test('works', () async { + test('dispose', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); - await controller.initialize(); + expect(controller.textureId, isNull); expect(await controller.position, const Duration(seconds: 0)); + await controller.initialize(); - await controller.seekTo(const Duration(milliseconds: 500)); + await controller.dispose(); - expect(await controller.position, const Duration(milliseconds: 500)); + expect(controller.textureId, isNotNull); + expect(await controller.position, isNull); }); - test('clamps values that are too high or low', () async { + test('play', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); - expect(await controller.position, const Duration(seconds: 0)); + expect(controller.value.isPlaying, isFalse); + await controller.play(); - await controller.seekTo(const Duration(seconds: 100)); - expect(await controller.position, const Duration(seconds: 1)); + expect(controller.value.isPlaying, isTrue); - await controller.seekTo(const Duration(seconds: -100)); - expect(await controller.position, const Duration(seconds: 0)); + // The two last calls will be "play" and then "setPlaybackSpeed". The + // reason for this is that "play" calls "setPlaybackSpeed" internally. + expect( + fakeVideoPlayerPlatform + .calls[fakeVideoPlayerPlatform.calls.length - 2], + 'play'); + expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed'); }); - }); - group('setVolume', () { - test('works', () async { + test('setLooping', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); - expect(controller.value.volume, 1.0); + expect(controller.value.isLooping, isFalse); + await controller.setLooping(true); - const double volume = 0.5; - await controller.setVolume(volume); - - expect(controller.value.volume, volume); + expect(controller.value.isLooping, isTrue); }); - test('clamps values that are too high or low', () async { + test('pause', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); - expect(controller.value.volume, 1.0); + await controller.play(); + expect(controller.value.isPlaying, isTrue); - await controller.setVolume(-1); - expect(controller.value.volume, 0.0); + await controller.pause(); - await controller.setVolume(11); - expect(controller.value.volume, 1.0); + expect(controller.value.isPlaying, isFalse); + expect(fakeVideoPlayerPlatform.calls.last, 'pause'); }); - }); - group('setPlaybackSpeed', () { - test('works', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.playbackSpeed, 1.0); + group('seekTo', () { + test('works', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(await controller.position, const Duration(seconds: 0)); - const double speed = 1.5; - await controller.setPlaybackSpeed(speed); + await controller.seekTo(const Duration(milliseconds: 500)); - expect(controller.value.playbackSpeed, speed); - }); + expect(await controller.position, const Duration(milliseconds: 500)); + }); - test('rejects negative values', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.playbackSpeed, 1.0); + test('clamps values that are too high or low', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(await controller.position, const Duration(seconds: 0)); - expect(() => controller.setPlaybackSpeed(-1), throwsArgumentError); - }); - }); + await controller.seekTo(const Duration(seconds: 100)); + expect(await controller.position, const Duration(seconds: 1)); - group('caption', () { - test('works when seeking', () async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - closedCaptionFile: _loadClosedCaption(), - ); + await controller.seekTo(const Duration(seconds: -100)); + expect(await controller.position, const Duration(seconds: 0)); + }); + }); - await controller.initialize(); - expect(controller.value.position, const Duration()); - expect(controller.value.caption.text, isNull); + group('setVolume', () { + test('works', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.volume, 1.0); - await controller.seekTo(const Duration(milliseconds: 100)); - expect(controller.value.caption.text, 'one'); + const double volume = 0.5; + await controller.setVolume(volume); - await controller.seekTo(const Duration(milliseconds: 250)); - expect(controller.value.caption.text, isNull); + expect(controller.value.volume, volume); + }); - await controller.seekTo(const Duration(milliseconds: 300)); - expect(controller.value.caption.text, 'two'); + test('clamps values that are too high or low', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.volume, 1.0); - await controller.seekTo(const Duration(milliseconds: 500)); - expect(controller.value.caption.text, isNull); + await controller.setVolume(-1); + expect(controller.value.volume, 0.0); - await controller.seekTo(const Duration(milliseconds: 300)); - expect(controller.value.caption.text, 'two'); + await controller.setVolume(11); + expect(controller.value.volume, 1.0); + }); }); - }); - group('Platform callbacks', () { - testWidgets('playing completed', (WidgetTester tester) async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.isPlaying, isFalse); - await controller.play(); - expect(controller.value.isPlaying, isTrue); - final FakeVideoEventStream fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]; - assert(fakeVideoEventStream != null); + group('setPlaybackSpeed', () { + test('works', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.playbackSpeed, 1.0); - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'completed'}); - await tester.pumpAndSettle(); + const double speed = 1.5; + await controller.setPlaybackSpeed(speed); - expect(controller.value.isPlaying, isFalse); - expect(controller.value.position, controller.value.duration); - }); + expect(controller.value.playbackSpeed, speed); + }); - testWidgets('buffering status', (WidgetTester tester) async { - final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - ); - await controller.initialize(); - expect(controller.value.isBuffering, false); - expect(controller.value.buffered, isEmpty); - final FakeVideoEventStream fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]; - assert(fakeVideoEventStream != null); - - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'bufferingStart'}); - await tester.pumpAndSettle(); - expect(controller.value.isBuffering, isTrue); - - const Duration bufferStart = Duration(seconds: 0); - const Duration bufferEnd = Duration(milliseconds: 500); - fakeVideoEventStream.eventsChannel.sendEvent({ - 'event': 'bufferingUpdate', - 'values': >[ - [bufferStart.inMilliseconds, bufferEnd.inMilliseconds] - ], + test('rejects negative values', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.playbackSpeed, 1.0); + + expect(() => controller.setPlaybackSpeed(-1), throwsArgumentError); }); - await tester.pumpAndSettle(); - expect(controller.value.isBuffering, isTrue); - expect(controller.value.buffered.length, 1); - expect(controller.value.buffered[0].toString(), - DurationRange(bufferStart, bufferEnd).toString()); - - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'bufferingEnd'}); - await tester.pumpAndSettle(); - expect(controller.value.isBuffering, isFalse); }); - }); - }); - group('DurationRange', () { - test('uses given values', () { - const Duration start = Duration(seconds: 2); - const Duration end = Duration(seconds: 8); + group('caption', () { + test('works when seeking', () async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + closedCaptionFile: _loadClosedCaption(), + ); - final DurationRange range = DurationRange(start, end); + await controller.initialize(); + expect(controller.value.position, const Duration()); + expect(controller.value.caption.text, isNull); - expect(range.start, start); - expect(range.end, end); - expect(range.toString(), contains('start: $start, end: $end')); - }); + await controller.seekTo(const Duration(milliseconds: 100)); + expect(controller.value.caption.text, 'one'); - test('calculates fractions', () { - const Duration start = Duration(seconds: 2); - const Duration end = Duration(seconds: 8); - const Duration total = Duration(seconds: 10); + await controller.seekTo(const Duration(milliseconds: 250)); + expect(controller.value.caption.text, isNull); - final DurationRange range = DurationRange(start, end); + await controller.seekTo(const Duration(milliseconds: 300)); + expect(controller.value.caption.text, 'two'); - expect(range.startFraction(total), .2); - expect(range.endFraction(total), .8); - }); - }); + await controller.seekTo(const Duration(milliseconds: 500)); + expect(controller.value.caption.text, isNull); - group('VideoPlayerValue', () { - test('uninitialized()', () { - final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized(); - - expect(uninitialized.duration, isNull); - expect(uninitialized.position, equals(const Duration(seconds: 0))); - expect(uninitialized.caption, equals(const Caption())); - expect(uninitialized.buffered, isEmpty); - expect(uninitialized.isPlaying, isFalse); - expect(uninitialized.isLooping, isFalse); - expect(uninitialized.isBuffering, isFalse); - expect(uninitialized.volume, 1.0); - expect(uninitialized.playbackSpeed, 1.0); - expect(uninitialized.errorDescription, isNull); - expect(uninitialized.size, isNull); - expect(uninitialized.size, isNull); - expect(uninitialized.initialized, isFalse); - expect(uninitialized.hasError, isFalse); - expect(uninitialized.aspectRatio, 1.0); - }); + await controller.seekTo(const Duration(milliseconds: 300)); + expect(controller.value.caption.text, 'two'); + }); + }); - test('erroneous()', () { - const String errorMessage = 'foo'; - final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage); - - expect(error.duration, isNull); - expect(error.position, equals(const Duration(seconds: 0))); - expect(error.caption, equals(const Caption())); - expect(error.buffered, isEmpty); - expect(error.isPlaying, isFalse); - expect(error.isLooping, isFalse); - expect(error.isBuffering, isFalse); - expect(error.volume, 1.0); - expect(error.playbackSpeed, 1.0); - expect(error.errorDescription, errorMessage); - expect(error.size, isNull); - expect(error.size, isNull); - expect(error.initialized, isFalse); - expect(error.hasError, isTrue); - expect(error.aspectRatio, 1.0); - }); + group('Platform callbacks', () { + testWidgets('playing completed', (WidgetTester tester) async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.isPlaying, isFalse); + await controller.play(); + expect(controller.value.isPlaying, isTrue); + final FakeVideoEventStream fakeVideoEventStream = + fakeVideoPlayerPlatform.streams[controller.textureId]; + assert(fakeVideoEventStream != null); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'completed'}); + await tester.pumpAndSettle(); + + expect(controller.value.isPlaying, isFalse); + expect(controller.value.position, controller.value.duration); + }); - test('toString()', () { - const Duration duration = Duration(seconds: 5); - const Size size = Size(400, 300); - const Duration position = Duration(seconds: 1); - const Caption caption = Caption(text: 'foo'); - final List buffered = [ - DurationRange(const Duration(seconds: 0), const Duration(seconds: 4)) - ]; - const bool isPlaying = true; - const bool isLooping = true; - const bool isBuffering = true; - const double volume = 0.5; - const double playbackSpeed = 1.5; - - final VideoPlayerValue value = VideoPlayerValue( - duration: duration, - size: size, - position: position, - caption: caption, - buffered: buffered, - isPlaying: isPlaying, - isLooping: isLooping, - isBuffering: isBuffering, - volume: volume, - playbackSpeed: playbackSpeed, - ); - - expect( - value.toString(), - 'VideoPlayerValue(duration: 0:00:05.000000, ' - 'size: Size(400.0, 300.0), ' - 'position: 0:00:01.000000, ' - 'caption: Instance of \'Caption\', ' - 'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], ' - 'isPlaying: true, ' - 'isLooping: true, ' - 'isBuffering: true, ' - 'volume: 0.5, ' - 'playbackSpeed: 1.5, ' - 'errorDescription: null)'); + testWidgets('buffering status', (WidgetTester tester) async { + final VideoPlayerController controller = + VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.isBuffering, false); + expect(controller.value.buffered, isEmpty); + final FakeVideoEventStream fakeVideoEventStream = + fakeVideoPlayerPlatform.streams[controller.textureId]; + assert(fakeVideoEventStream != null); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'bufferingStart'}); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isTrue); + + const Duration bufferStart = Duration(seconds: 0); + const Duration bufferEnd = Duration(milliseconds: 500); + fakeVideoEventStream.eventsChannel.sendEvent({ + 'event': 'bufferingUpdate', + 'values': >[ + [bufferStart.inMilliseconds, bufferEnd.inMilliseconds] + ], + }); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isTrue); + expect(controller.value.buffered.length, 1); + expect(controller.value.buffered[0].toString(), + DurationRange(bufferStart, bufferEnd).toString()); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'bufferingEnd'}); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isFalse); + }); + }); }); - test('copyWith()', () { - final VideoPlayerValue original = VideoPlayerValue.uninitialized(); - final VideoPlayerValue exactCopy = original.copyWith(); + group('DurationRange', () { + test('uses given values', () { + const Duration start = Duration(seconds: 2); + const Duration end = Duration(seconds: 8); + + final DurationRange range = DurationRange(start, end); + + expect(range.start, start); + expect(range.end, end); + expect(range.toString(), contains('start: $start, end: $end')); + }); + + test('calculates fractions', () { + const Duration start = Duration(seconds: 2); + const Duration end = Duration(seconds: 8); + const Duration total = Duration(seconds: 10); + + final DurationRange range = DurationRange(start, end); - expect(exactCopy.toString(), original.toString()); + expect(range.startFraction(total), .2); + expect(range.endFraction(total), .8); + }); }); - group('aspectRatio', () { - test('640x480 -> 4:3', () { - final value = VideoPlayerValue( - size: Size(640, 480), - duration: Duration(seconds: 1), - ); - expect(value.aspectRatio, 4 / 3); + group('VideoPlayerValue', () { + test('uninitialized()', () { + final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized(); + + expect(uninitialized.duration, isNull); + expect(uninitialized.position, equals(const Duration(seconds: 0))); + expect(uninitialized.caption, equals(const Caption())); + expect(uninitialized.buffered, isEmpty); + expect(uninitialized.isPlaying, isFalse); + expect(uninitialized.isLooping, isFalse); + expect(uninitialized.isBuffering, isFalse); + expect(uninitialized.volume, 1.0); + expect(uninitialized.playbackSpeed, 1.0); + expect(uninitialized.errorDescription, isNull); + expect(uninitialized.size, isNull); + expect(uninitialized.size, isNull); + expect(uninitialized.initialized, isFalse); + expect(uninitialized.hasError, isFalse); + expect(uninitialized.aspectRatio, 1.0); }); - test('null size -> 1.0', () { - final value = VideoPlayerValue( - size: null, - duration: Duration(seconds: 1), - ); - expect(value.aspectRatio, 1.0); + test('erroneous()', () { + const String errorMessage = 'foo'; + final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage); + + expect(error.duration, isNull); + expect(error.position, equals(const Duration(seconds: 0))); + expect(error.caption, equals(const Caption())); + expect(error.buffered, isEmpty); + expect(error.isPlaying, isFalse); + expect(error.isLooping, isFalse); + expect(error.isBuffering, isFalse); + expect(error.volume, 1.0); + expect(error.playbackSpeed, 1.0); + expect(error.errorDescription, errorMessage); + expect(error.size, isNull); + expect(error.size, isNull); + expect(error.initialized, isFalse); + expect(error.hasError, isTrue); + expect(error.aspectRatio, 1.0); }); - test('height = 0 -> 1.0', () { - final value = VideoPlayerValue( - size: Size(640, 0), - duration: Duration(seconds: 1), + test('toString()', () { + const Duration duration = Duration(seconds: 5); + const Size size = Size(400, 300); + const Duration position = Duration(seconds: 1); + const Caption caption = Caption(text: 'foo'); + final List buffered = [ + DurationRange(const Duration(seconds: 0), const Duration(seconds: 4)) + ]; + const bool isPlaying = true; + const bool isLooping = true; + const bool isBuffering = true; + const double volume = 0.5; + const double playbackSpeed = 1.5; + + final VideoPlayerValue value = VideoPlayerValue( + duration: duration, + size: size, + position: position, + caption: caption, + buffered: buffered, + isPlaying: isPlaying, + isLooping: isLooping, + isBuffering: isBuffering, + volume: volume, + playbackSpeed: playbackSpeed, ); - expect(value.aspectRatio, 1.0); + + expect( + value.toString(), + 'VideoPlayerValue(duration: 0:00:05.000000, ' + 'size: Size(400.0, 300.0), ' + 'position: 0:00:01.000000, ' + 'caption: Instance of \'Caption\', ' + 'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], ' + 'isPlaying: true, ' + 'isLooping: true, ' + 'isBuffering: true, ' + 'volume: 0.5, ' + 'playbackSpeed: 1.5, ' + 'errorDescription: null)'); }); - test('width = 0 -> 1.0', () { - final value = VideoPlayerValue( - size: Size(0, 480), - duration: Duration(seconds: 1), - ); - expect(value.aspectRatio, 1.0); + test('copyWith()', () { + final VideoPlayerValue original = VideoPlayerValue.uninitialized(); + final VideoPlayerValue exactCopy = original.copyWith(); + + expect(exactCopy.toString(), original.toString()); }); - test('negative aspect ratio -> 1.0', () { - final value = VideoPlayerValue( - size: Size(640, -480), - duration: Duration(seconds: 1), - ); - expect(value.aspectRatio, 1.0); + group('aspectRatio', () { + test('640x480 -> 4:3', () { + final value = VideoPlayerValue( + size: Size(640, 480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 4 / 3); + }); + + test('null size -> 1.0', () { + final value = VideoPlayerValue( + size: null, + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('height = 0 -> 1.0', () { + final value = VideoPlayerValue( + size: Size(640, 0), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('width = 0 -> 1.0', () { + final value = VideoPlayerValue( + size: Size(0, 480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('negative aspect ratio -> 1.0', () { + final value = VideoPlayerValue( + size: Size(640, -480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); }); }); - }); - test('VideoProgressColors', () { - const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75); - const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5); - const Color backgroundColor = Color.fromRGBO(255, 255, 0, 0.25); + test('VideoProgressColors', () { + const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75); + const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5); + const Color backgroundColor = Color.fromRGBO(255, 255, 0, 0.25); - final VideoProgressColors colors = VideoProgressColors( - playedColor: playedColor, - bufferedColor: bufferedColor, - backgroundColor: backgroundColor); + final VideoProgressColors colors = VideoProgressColors( + playedColor: playedColor, + bufferedColor: bufferedColor, + backgroundColor: backgroundColor); - expect(colors.playedColor, playedColor); - expect(colors.bufferedColor, bufferedColor); - expect(colors.backgroundColor, backgroundColor); - }); + expect(colors.playedColor, playedColor); + expect(colors.bufferedColor, bufferedColor); + expect(colors.backgroundColor, backgroundColor); + }); - test('setMixWithOthers', () async { - final VideoPlayerController controller = VideoPlayerController.file( - File(''), - videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); - await controller.initialize(); - expect(controller.videoPlayerOptions.mixWithOthers, true); + test('setMixWithOthers', () async { + final VideoPlayerController controller = VideoPlayerController.file( + File(''), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); + await controller.initialize(); + expect(controller.videoPlayerOptions.mixWithOthers, true); + }); }); } @@ -680,7 +732,7 @@ class FakeVideoPlayerPlatform extends TestHostVideoPlayerApi { } @override - void initialize() { + void initialize(InitializeMessage arg) { calls.add('init'); initialized.complete(true); } diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 8af22f783675..218a03f854ea 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +* Add caching functionality to videos from network sources. + ## 2.2.0 * Added option to set the video playback speed on the video controller. diff --git a/packages/video_player/video_player_platform_interface/lib/messages.dart b/packages/video_player/video_player_platform_interface/lib/messages.dart index bfe65f1fd2ea..a6a457d07b06 100644 --- a/packages/video_player/video_player_platform_interface/lib/messages.dart +++ b/packages/video_player/video_player_platform_interface/lib/messages.dart @@ -6,6 +6,29 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; +class InitializeMessage { + int maxCacheSize; + int maxCacheFileSize; + // ignore: unused_element + Map _toMap() { + final Map pigeonMap = {}; + pigeonMap['maxCacheSize'] = maxCacheSize; + pigeonMap['maxCacheFileSize'] = maxCacheFileSize; + return pigeonMap; + } + + // ignore: unused_element + static InitializeMessage _fromMap(Map pigeonMap) { + if (pigeonMap == null) { + return null; + } + final InitializeMessage result = InitializeMessage(); + result.maxCacheSize = pigeonMap['maxCacheSize']; + result.maxCacheFileSize = pigeonMap['maxCacheFileSize']; + return result; + } +} + class TextureMessage { int textureId; // ignore: unused_element @@ -31,6 +54,7 @@ class CreateMessage { String uri; String packageName; String formatHint; + bool useCache; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -38,6 +62,7 @@ class CreateMessage { pigeonMap['uri'] = uri; pigeonMap['packageName'] = packageName; pigeonMap['formatHint'] = formatHint; + pigeonMap['useCache'] = useCache; return pigeonMap; } @@ -51,6 +76,7 @@ class CreateMessage { result.uri = pigeonMap['uri']; result.packageName = pigeonMap['packageName']; result.formatHint = pigeonMap['formatHint']; + result.useCache = pigeonMap['useCache']; return result; } } @@ -168,11 +194,12 @@ class MixWithOthersMessage { } class VideoPlayerApi { - Future initialize() async { + Future initialize(InitializeMessage arg) async { + final Map requestMap = arg._toMap(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec()); - final Map replyMap = await channel.send(null); + final Map replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -413,7 +440,7 @@ class VideoPlayerApi { } abstract class TestHostVideoPlayerApi { - void initialize(); + void initialize(InitializeMessage arg); TextureMessage create(CreateMessage arg); void dispose(TextureMessage arg); void setLooping(LoopingMessage arg); @@ -430,7 +457,10 @@ abstract class TestHostVideoPlayerApi { 'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec()); channel.setMockMessageHandler((dynamic message) async { - api.initialize(); + final Map mapMessage = + message as Map; + final InitializeMessage input = InitializeMessage._fromMap(mapMessage); + api.initialize(input); return {}; }); } diff --git a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart index 0ea443fb6e12..021d2ed06c89 100644 --- a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart +++ b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart @@ -16,8 +16,11 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { VideoPlayerApi _api = VideoPlayerApi(); @override - Future init() { - return _api.initialize(); + Future init(int maxCacheSize, int maxCacheFileSize) { + InitializeMessage message = InitializeMessage(); + message.maxCacheSize = maxCacheSize; + message.maxCacheFileSize = maxCacheFileSize; + return _api.initialize(message); } @override @@ -37,6 +40,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { case DataSourceType.network: message.uri = dataSource.uri; message.formatHint = _videoFormatStringMap[dataSource.formatHint]; + message.useCache = dataSource.useCache; break; case DataSourceType.file: message.uri = dataSource.uri; diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 2757fb135af6..638d6acc1240 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -56,7 +56,7 @@ abstract class VideoPlayerPlatform { /// /// This method is called when the plugin is first initialized /// and on every full restart. - Future init() { + Future init(int maxCacheSize, int maxCacheFileSize) { throw UnimplementedError('init() has not been implemented.'); } @@ -145,13 +145,16 @@ class DataSource { /// /// The [package] argument must be non-null when the asset comes from a /// package and null otherwise. + /// + /// The [useCache] argument must be non-null, default is false. DataSource({ @required this.sourceType, this.uri, this.formatHint, this.asset, this.package, - }); + this.useCache = false, + }) : assert(useCache != null); /// The way in which the video was originally loaded. /// @@ -175,6 +178,9 @@ class DataSource { /// The package that the asset was loaded from. Only set for /// [DataSourceType.asset] videos. final String package; + + /// Use cache for this data source or not. Used only for network data source. + final bool useCache; } /// The way in which the video was originally loaded. diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index cc3cd79f1f33..430a63fb93e3 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the video_player plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.2.0 +version: 2.3.0 dependencies: flutter: diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart index c4791001ad92..3e2bf7ed003f 100644 --- a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart +++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart @@ -13,6 +13,7 @@ import 'package:video_player_platform_interface/video_player_platform_interface. class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; + InitializeMessage initializeMessage; TextureMessage textureMessage; CreateMessage createMessage; PositionMessage positionMessage; @@ -35,8 +36,9 @@ class _ApiLogger implements TestHostVideoPlayerApi { } @override - void initialize() { + void initialize(InitializeMessage arg) { log.add('init'); + initializeMessage = arg; } @override @@ -126,11 +128,13 @@ void main() { }); test('init', () async { - await player.init(); + await player.init(100, 10); expect( log.log.last, 'init', ); + expect(log.initializeMessage.maxCacheSize, 100); + expect(log.initializeMessage.maxCacheFileSize, 10); }); test('dispose', () async { @@ -160,9 +164,75 @@ void main() { expect(log.log.last, 'create'); expect(log.createMessage.uri, 'someUri'); expect(log.createMessage.formatHint, 'dash'); + expect(log.createMessage.useCache, false); expect(textureId, 3); }); + group('create with network', () { + test('with cache', () async { + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + useCache: true, + ), + ); + + expect(log.log.last, 'create'); + expect(log.createMessage.uri, 'someUri'); + expect(log.createMessage.formatHint, null); + expect(log.createMessage.useCache, true); + expect(textureId, 3); + }); + + test('without cache', () async { + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + useCache: false, + ), + ); + + expect(log.log.last, 'create'); + expect(log.createMessage.uri, 'someUri'); + expect(log.createMessage.formatHint, null); + expect(log.createMessage.useCache, false); + expect(textureId, 3); + }); + + test('without cache by default', () async { + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + ), + ); + + expect(log.log.last, 'create'); + expect(log.createMessage.uri, 'someUri'); + expect(log.createMessage.formatHint, null); + expect(log.createMessage.useCache, false); + expect(textureId, 3); + }); + + test('with hint', () async { + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + formatHint: VideoFormat.dash, + ), + ); + + expect(log.log.last, 'create'); + expect(log.createMessage.uri, 'someUri'); + expect(log.createMessage.formatHint, 'dash'); + expect(log.createMessage.useCache, false); + expect(textureId, 3); + }); + }); + test('create with file', () async { final int textureId = await player.create(DataSource( sourceType: DataSourceType.file, diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index d18504913d89..39083da6e632 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.5 + +- bump video_player_platform_interface to 2.3.0 + ## 0.1.4 * Added option to set the video playback speed on the video controller. diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 251da3779e7f..10bb9d0a55c5 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -44,7 +44,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { int _textureCounter = 1; @override - Future init() async { + Future init(int maxCacheSize, int maxCacheFileSize) async { return _disposeAllPlayers(); } diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 98191bf6ba85..751e6a8091d9 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/v # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.4 +version: 0.1.5 flutter: plugin: @@ -19,7 +19,12 @@ dependencies: flutter_web_plugins: sdk: flutter meta: ^1.1.7 - video_player_platform_interface: ^2.2.0 + video_player_platform_interface: #^2.3.0 + # workaround for success build checks in PR + git: + url: git://github.com/sanekyy/plugins.git + ref: caching + path: packages/video_player/video_player_platform_interface dev_dependencies: flutter_test: diff --git a/packages/video_player/video_player_web/test/video_player_web_test.dart b/packages/video_player/video_player_web/test/video_player_web_test.dart index 453079bfcd40..04db00af40c1 100644 --- a/packages/video_player/video_player_web/test/video_player_web_test.dart +++ b/packages/video_player/video_player_web/test/video_player_web_test.dart @@ -31,7 +31,7 @@ void main() { }); test('can init', () { - expect(VideoPlayerPlatform.instance.init(), completes); + expect(VideoPlayerPlatform.instance.init(100, 10), completes); }); test('can create from network', () {