diff --git a/AUTHORS b/AUTHORS index b27c156188f8..14a642adc28f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,4 +56,6 @@ Giancarlo Rocha Ryo Miyake Théo Champion Kazuki Yamaguchi -Eitan Schwartz \ No newline at end of file +Eitan Schwartz +Aleksandr Yurkovskiy +Iurii Dorofeev \ No newline at end of file diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 2a1f692092aa..2d2130b4dbc0 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.10 + +* Add caching functionality to videos from network sources. + ## 0.10.9+1 * Readme updated to include web support and details on how to use for web @@ -21,7 +25,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/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 43123ef09238..7c5eeb1ebd19 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 @@ -26,13 +26,20 @@ 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.plugin.common.MethodChannel.Result; import io.flutter.view.TextureRegistry; +import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -62,8 +69,11 @@ final class VideoPlayer { EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, String dataSource, - Result result, - String formatHint) { + String formatHint, + int maxCacheSize, + int maxCacheFileSize, + boolean useCache, + Result result) { this.eventChannel = eventChannel; this.textureEntry = textureEntry; @@ -81,6 +91,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"); } @@ -280,4 +294,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 57707029f1f2..fd53dd81aa69 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 @@ -24,6 +24,9 @@ public class VideoPlayerPlugin implements MethodCallHandler, FlutterPlugin { private final LongSparseArray videoPlayers = new LongSparseArray<>(); private FlutterState flutterState; + private int maxCacheSize; + private int maxCacheFileSize; + /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ public VideoPlayerPlugin() {} @@ -93,7 +96,10 @@ public void onMethodCall(MethodCall call, Result result) { } switch (call.method) { case "init": + maxCacheSize = call.argument("maxCacheSize"); + maxCacheFileSize = call.argument("maxCacheFileSize"); disposeAllPlayers(); + result.success(null); break; case "create": { @@ -119,9 +125,11 @@ public void onMethodCall(MethodCall call, Result result) { eventChannel, handle, "asset:///" + assetLookupKey, - result, - null); - videoPlayers.put(handle.id(), player); + null, + maxCacheSize, + maxCacheFileSize, + false, + result); } else { player = new VideoPlayer( @@ -129,10 +137,13 @@ public void onMethodCall(MethodCall call, Result result) { eventChannel, handle, call.argument("uri"), - result, - call.argument("formatHint")); - videoPlayers.put(handle.id(), player); + call.argument("formatHint"), + maxCacheSize, + maxCacheFileSize, + call.argument("useCache"), + result); } + videoPlayers.put(handle.id(), player); break; } default: diff --git a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m index 39313fa18496..6f1ef7b8dc0c 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" #if !__has_feature(objc_arc) #error Code Requires ARC. @@ -50,6 +51,7 @@ - (void)play; - (void)pause; - (void)setIsLooping:(bool)isLooping; - (void)updatePlayingState; ++ (VIResourceLoaderManager*)resourceLoaderManager; @end static void* timeRangeContext = &timeRangeContext; @@ -162,10 +164,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? @@ -425,6 +446,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 @@ -480,6 +503,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [_players[textureId] dispose]; } [_players removeAllObjects]; + NSDictionary* argsMap = call.arguments; + _maxCacheSize = ((NSNumber*)argsMap[@"maxCacheSize"]).longValue; + _maxCacheFileSize = ((NSNumber*)argsMap[@"maxCacheFileSize"]).longValue; result(nil); } else if ([@"create" isEqualToString:call.method]) { NSDictionary* argsMap = call.arguments; @@ -498,8 +524,20 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; } else if (uriArg) { - player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:uriArg] - frameUpdater:frameUpdater]; + BOOL useCache = [argsMap[@"useCache"] boolValue]; + BOOL enableCache = _maxCacheSize > 0 && _maxCacheFileSize > 0 && useCache; + if (enableCache) { + NSString* escapedURL = [uriArg + stringByAddingPercentEncodingWithAllowedCharacters:NSMutableCharacterSet + .alphanumericCharacterSet]; + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:escapedURL] + frameUpdater:frameUpdater + enableCache:enableCache]; + } else { + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:uriArg] + frameUpdater:frameUpdater]; + } + [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; } else { result(FlutterMethodNotImplemented); diff --git a/packages/video_player/video_player/ios/video_player.podspec b/packages/video_player/video_player/ios/video_player.podspec index bd21f4c15365..9cf2747c8df0 100644 --- a/packages/video_player/video_player/ios/video_player.podspec +++ b/packages/video_player/video_player/ios/video_player.podspec @@ -17,6 +17,7 @@ Downloaded by pub (not CocoaPods). s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' + s.dependency 'VIMediaCache' s.platform = :ios, '8.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => '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 f2f9289c1fda..96ba915e1b27 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -6,22 +6,17 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; - import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + export 'package:video_player_platform_interface/video_player_platform_interface.dart' show DurationRange, DataSourceType, VideoFormat; 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 { @@ -171,6 +166,7 @@ class VideoPlayerController extends ValueNotifier { {this.package, this.closedCaptionFile}) : dataSourceType = DataSourceType.asset, formatHint = null, + useCache = null, super(VideoPlayerValue(duration: null)); /// Constructs a [VideoPlayerController] playing a video from obtained from @@ -179,11 +175,17 @@ 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}) - : 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, + 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. @@ -195,8 +197,15 @@ class VideoPlayerController extends ValueNotifier { 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 @@ -211,6 +220,9 @@ class VideoPlayerController extends ValueNotifier { /// is constructed with. final DataSourceType dataSourceType; + /// 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; @@ -224,6 +236,7 @@ class VideoPlayerController extends ValueNotifier { ClosedCaptionFile _closedCaptionFile; Timer _timer; bool _isDisposed = false; + static Completer _pluginInitializingCompleter; Completer _creatingCompleter; StreamSubscription _eventSubscription; _VideoAppLifeCycleObserver _lifeCycleObserver; @@ -233,8 +246,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(); @@ -253,6 +288,7 @@ class VideoPlayerController extends ValueNotifier { sourceType: DataSourceType.network, uri: dataSource, formatHint: formatHint, + useCache: useCache, ); break; case DataSourceType.file: @@ -262,7 +298,9 @@ class VideoPlayerController extends ValueNotifier { ); break; } - _textureId = await _videoPlayerPlatform.create(dataSourceDescription); + + _textureId = + await VideoPlayerPlatform.instance.create(dataSourceDescription); _creatingCompleter.complete(null); final Completer initializingCompleter = Completer(); @@ -316,12 +354,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) { @@ -330,7 +381,7 @@ class VideoPlayerController extends ValueNotifier { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); - await _videoPlayerPlatform.dispose(_textureId); + await VideoPlayerPlatform.instance.dispose(_textureId); } _lifeCycleObserver.dispose(); } @@ -365,7 +416,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 { @@ -373,7 +424,7 @@ class VideoPlayerController extends ValueNotifier { return; } if (value.isPlaying) { - await _videoPlayerPlatform.play(_textureId); + await VideoPlayerPlatform.instance.play(_textureId); _timer = Timer.periodic( const Duration(milliseconds: 500), (Timer timer) async { @@ -389,7 +440,7 @@ class VideoPlayerController extends ValueNotifier { ); } else { _timer?.cancel(); - await _videoPlayerPlatform.pause(_textureId); + await VideoPlayerPlatform.instance.pause(_textureId); } } @@ -397,7 +448,7 @@ class VideoPlayerController extends ValueNotifier { if (!value.initialized || _isDisposed) { return; } - await _videoPlayerPlatform.setVolume(_textureId, value.volume); + await VideoPlayerPlatform.instance.setVolume(_textureId, value.volume); } /// The position in the current video. @@ -405,7 +456,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 @@ -422,7 +473,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); } @@ -549,7 +600,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/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index ed57f47a8411..cae392268d81 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -1,7 +1,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. -version: 0.10.9+1 +version: 0.10.10 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: @@ -17,13 +17,23 @@ flutter: dependencies: meta: "^1.0.5" - video_player_platform_interface: ^1.0.1 + video_player_platform_interface: #^1.0.6 + # 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.1 + video_player_web: #^0.1.2+3 + # 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 20413b5564c5..c8f4597fed7e 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -4,12 +4,13 @@ import 'dart:async'; import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:video_player/video_player.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player/video_player.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; class FakeController extends ValueNotifier @@ -26,23 +27,31 @@ class FakeController extends ValueNotifier @override String get dataSource => ''; + @override DataSourceType get dataSourceType => DataSourceType.file; + @override String get package => null; + @override Future get position async => value.position; @override Future seekTo(Duration moment) async {} + @override Future setVolume(double volume) async {} + @override Future initialize() async {} + @override Future pause() async {} + @override Future play() async {} + @override Future setLooping(bool looping) async {} @@ -51,6 +60,9 @@ class FakeController extends ValueNotifier @override Future get closedCaptionFile => _loadClosedCaption(); + + @override + bool get useCache => false; } Future _loadClosedCaption() async => @@ -176,32 +188,71 @@ void main() { }); }); - 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', + 'formatHint': null, + 'useCache': true, + }); + }); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], - { - 'uri': 'https://127.0.0.1', - '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', + 'formatHint': null, + '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', + 'formatHint': null, + 'useCache': false, + }); + }); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], - { - 'uri': 'https://127.0.0.1', - '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', + 'formatHint': 'dash', + 'useCache': false, + }); + }); }); test('init errors', () async { diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index be1b0e385dd9..e82b4d95cb62 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 @@ +## 1.0.6 + +* Add caching functionality to videos from network sources. + ## 1.0.5 * Make the pedantic dev_dependency explicit. 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 eb227ce18ec5..014f8c32a42d 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 @@ -15,8 +15,11 @@ const MethodChannel _channel = MethodChannel('flutter.io/videoPlayer'); /// An implementation of [VideoPlayerPlatform] that uses method channels. class MethodChannelVideoPlayer extends VideoPlayerPlatform { @override - Future init() { - return _channel.invokeMethod('init'); + Future init(int maxCacheSize, int maxCacheFileSize) { + return _channel.invokeMethod('init', { + 'maxCacheSize': maxCacheSize, + 'maxCacheFileSize': maxCacheFileSize, + }); } @override @@ -41,11 +44,16 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { case DataSourceType.network: dataSourceDescription = { 'uri': dataSource.uri, - 'formatHint': _videoFormatStringMap[dataSource.formatHint] + 'formatHint': _videoFormatStringMap[dataSource.formatHint], + 'useCache': dataSource.useCache, }; break; case DataSourceType.file: - dataSourceDescription = {'uri': dataSource.uri}; + dataSourceDescription = { + 'uri': dataSource.uri, + 'formatHint': _videoFormatStringMap[dataSource.formatHint], + 'useCache': dataSource.useCache, + }; break; } 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 4c1f2b67c4fc..8ec222ea8569 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.'); } @@ -135,13 +135,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. /// @@ -165,6 +168,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 2ce3bd17930d..68183018cc20 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: 1.0.5 +version: 1.0.6 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 a5fdbbc257de..cd234e157f1d 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 @@ -4,10 +4,9 @@ import 'dart:ui'; -import 'package:mockito/mockito.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - +import 'package:mockito/mockito.dart'; import 'package:video_player_platform_interface/method_channel_video_player.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; @@ -54,10 +53,15 @@ void main() { }); test('init', () async { - await player.init(); + await player.init(100, 10); expect( log, - [isMethodCall('init', arguments: null)], + [ + isMethodCall('init', arguments: { + 'maxCacheSize': 100, + 'maxCacheFileSize': 10, + }) + ], ); }); @@ -110,13 +114,119 @@ void main() { [ isMethodCall('create', arguments: { 'uri': 'someUri', - 'formatHint': 'dash' + 'formatHint': 'dash', + 'useCache': false, }) ], ); expect(textureId, 3); }); + group('create with network', () { + test('with cache', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + useCache: true, + ), + ); + + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + 'formatHint': null, + 'useCache': true, + }) + ], + ); + expect(textureId, 3); + }); + + test('without cache', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + useCache: false, + ), + ); + + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + 'formatHint': null, + 'useCache': false, + }) + ], + ); + expect(textureId, 3); + }); + + test('without cache by default', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + ), + ); + + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + 'formatHint': null, + 'useCache': false, + }) + ], + ); + expect(textureId, 3); + }); + + test('with hint', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + formatHint: VideoFormat.dash, + ), + ); + + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + 'formatHint': 'dash', + 'useCache': false, + }) + ], + ); + expect(textureId, 3); + }); + }); + test('create with file', () async { channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index f5408fa636ce..04752c13e6cc 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.2+3 + +- bump video_player_platform_interface to 1.0.6 + ## 0.1.2+2 * Add `analysis_options.yaml` to the package, so we can ignore `undefined_prefixed_name` errors. Works around https://github.com/flutter/flutter/issues/41563. 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 039c3ce65a7e..4100a7058bdd 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 7cf036ef5b4b..1ebe50c6f157 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -1,7 +1,7 @@ name: video_player_web description: Web platform implementation of video_player homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web -version: 0.1.2+2 +version: 0.1.2+3 flutter: plugin: @@ -16,7 +16,12 @@ dependencies: flutter_web_plugins: sdk: flutter meta: ^1.1.7 - video_player_platform_interface: ^1.0.0 + video_player_platform_interface: #^1.0.6 + # 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 ef6dc028c529..631d461c37e2 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', () {