[video_player] Add v2 embedding support (#2226)
- Adds support in the plugin
- Adds a v2 embedding to the example app
- Fixes a broken remote example in the example app
- Increments the Flutter SDK dependency
- Increments the version
- Adds e2e tests for some simple use cases of the plugin
diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md
index d488249..759c955 100644
--- a/packages/video_player/CHANGELOG.md
+++ b/packages/video_player/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.10.3
+
+* Add support for the v2 Android embedding. This shouldn't impact existing
+ functionality.
+
## 0.10.2+6
* Remove AndroidX warnings.
diff --git a/packages/video_player/android/build.gradle b/packages/video_player/android/build.gradle
index edbb4c7..4a73ec5 100644
--- a/packages/video_player/android/build.gradle
+++ b/packages/video_player/android/build.gradle
@@ -45,3 +45,29 @@
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.9.6'
}
}
+
+// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
+afterEvaluate {
+ def containsEmbeddingDependencies = false
+ for (def configuration : configurations.all) {
+ for (def dependency : configuration.dependencies) {
+ if (dependency.group == 'io.flutter' &&
+ dependency.name.startsWith('flutter_embedding') &&
+ dependency.isTransitive())
+ {
+ containsEmbeddingDependencies = true
+ break
+ }
+ }
+ }
+ if (!containsEmbeddingDependencies) {
+ android {
+ dependencies {
+ def lifecycle_version = "1.1.1"
+ compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
+ compileOnly "android.arch.lifecycle:common:$lifecycle_version"
+ compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
+ }
+ }
+ }
+}
diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
new file mode 100644
index 0000000..43123ef
--- /dev/null
+++ b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
@@ -0,0 +1,283 @@
+package io.flutter.plugins.videoplayer;
+
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.view.Surface;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.EventListener;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
+import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
+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.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+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.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+final class VideoPlayer {
+ private static final String FORMAT_SS = "ss";
+ private static final String FORMAT_DASH = "dash";
+ private static final String FORMAT_HLS = "hls";
+ private static final String FORMAT_OTHER = "other";
+
+ private SimpleExoPlayer exoPlayer;
+
+ private Surface surface;
+
+ private final TextureRegistry.SurfaceTextureEntry textureEntry;
+
+ private QueuingEventSink eventSink = new QueuingEventSink();
+
+ private final EventChannel eventChannel;
+
+ private boolean isInitialized = false;
+
+ VideoPlayer(
+ Context context,
+ EventChannel eventChannel,
+ TextureRegistry.SurfaceTextureEntry textureEntry,
+ String dataSource,
+ Result result,
+ String formatHint) {
+ this.eventChannel = eventChannel;
+ this.textureEntry = textureEntry;
+
+ TrackSelector trackSelector = new DefaultTrackSelector();
+ exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
+
+ Uri uri = Uri.parse(dataSource);
+
+ DataSource.Factory dataSourceFactory;
+ if (isHTTP(uri)) {
+ dataSourceFactory =
+ new DefaultHttpDataSourceFactory(
+ "ExoPlayer",
+ null,
+ DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
+ DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
+ true);
+ } else {
+ dataSourceFactory = new DefaultDataSourceFactory(context, "ExoPlayer");
+ }
+
+ MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context);
+ exoPlayer.prepare(mediaSource);
+
+ setupVideoPlayer(eventChannel, textureEntry, result);
+ }
+
+ private static boolean isHTTP(Uri uri) {
+ if (uri == null || uri.getScheme() == null) {
+ return false;
+ }
+ String scheme = uri.getScheme();
+ return scheme.equals("http") || scheme.equals("https");
+ }
+
+ private MediaSource buildMediaSource(
+ Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) {
+ int type;
+ if (formatHint == null) {
+ type = Util.inferContentType(uri.getLastPathSegment());
+ } else {
+ switch (formatHint) {
+ case FORMAT_SS:
+ type = C.TYPE_SS;
+ break;
+ case FORMAT_DASH:
+ type = C.TYPE_DASH;
+ break;
+ case FORMAT_HLS:
+ type = C.TYPE_HLS;
+ break;
+ case FORMAT_OTHER:
+ type = C.TYPE_OTHER;
+ break;
+ default:
+ type = -1;
+ break;
+ }
+ }
+ switch (type) {
+ case C.TYPE_SS:
+ return new SsMediaSource.Factory(
+ new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
+ new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
+ .createMediaSource(uri);
+ case C.TYPE_DASH:
+ return new DashMediaSource.Factory(
+ new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
+ new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
+ .createMediaSource(uri);
+ case C.TYPE_HLS:
+ return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
+ case C.TYPE_OTHER:
+ return new ExtractorMediaSource.Factory(mediaDataSourceFactory)
+ .setExtractorsFactory(new DefaultExtractorsFactory())
+ .createMediaSource(uri);
+ default:
+ {
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+ }
+
+ private void setupVideoPlayer(
+ EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, Result result) {
+
+ eventChannel.setStreamHandler(
+ new EventChannel.StreamHandler() {
+ @Override
+ public void onListen(Object o, EventChannel.EventSink sink) {
+ eventSink.setDelegate(sink);
+ }
+
+ @Override
+ public void onCancel(Object o) {
+ eventSink.setDelegate(null);
+ }
+ });
+
+ surface = new Surface(textureEntry.surfaceTexture());
+ exoPlayer.setVideoSurface(surface);
+ setAudioAttributes(exoPlayer);
+
+ exoPlayer.addListener(
+ new EventListener() {
+
+ @Override
+ public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
+ if (playbackState == Player.STATE_BUFFERING) {
+ sendBufferingUpdate();
+ } else if (playbackState == Player.STATE_READY) {
+ if (!isInitialized) {
+ isInitialized = true;
+ sendInitialized();
+ }
+ } else if (playbackState == Player.STATE_ENDED) {
+ Map<String, Object> event = new HashMap<>();
+ event.put("event", "completed");
+ eventSink.success(event);
+ }
+ }
+
+ @Override
+ public void onPlayerError(final ExoPlaybackException error) {
+ if (eventSink != null) {
+ eventSink.error("VideoError", "Video player had error " + error, null);
+ }
+ }
+ });
+
+ Map<String, Object> reply = new HashMap<>();
+ reply.put("textureId", textureEntry.id());
+ result.success(reply);
+ }
+
+ void sendBufferingUpdate() {
+ Map<String, Object> event = new HashMap<>();
+ event.put("event", "bufferingUpdate");
+ List<? extends Number> range = Arrays.asList(0, exoPlayer.getBufferedPosition());
+ // iOS supports a list of buffered ranges, so here is a list with a single range.
+ event.put("values", Collections.singletonList(range));
+ eventSink.success(event);
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setAudioAttributes(SimpleExoPlayer exoPlayer) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ exoPlayer.setAudioAttributes(
+ new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MOVIE).build());
+ } else {
+ exoPlayer.setAudioStreamType(C.STREAM_TYPE_MUSIC);
+ }
+ }
+
+ void play() {
+ exoPlayer.setPlayWhenReady(true);
+ }
+
+ void pause() {
+ exoPlayer.setPlayWhenReady(false);
+ }
+
+ void setLooping(boolean value) {
+ exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF);
+ }
+
+ void setVolume(double value) {
+ float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value));
+ exoPlayer.setVolume(bracketedValue);
+ }
+
+ void seekTo(int location) {
+ exoPlayer.seekTo(location);
+ }
+
+ long getPosition() {
+ return exoPlayer.getCurrentPosition();
+ }
+
+ @SuppressWarnings("SuspiciousNameCombination")
+ private void sendInitialized() {
+ if (isInitialized) {
+ Map<String, Object> event = new HashMap<>();
+ event.put("event", "initialized");
+ event.put("duration", exoPlayer.getDuration());
+
+ if (exoPlayer.getVideoFormat() != null) {
+ Format videoFormat = exoPlayer.getVideoFormat();
+ int width = videoFormat.width;
+ int height = videoFormat.height;
+ int rotationDegrees = videoFormat.rotationDegrees;
+ // Switch the width/height if video was taken in portrait mode
+ if (rotationDegrees == 90 || rotationDegrees == 270) {
+ width = exoPlayer.getVideoFormat().height;
+ height = exoPlayer.getVideoFormat().width;
+ }
+ event.put("width", width);
+ event.put("height", height);
+ }
+ eventSink.success(event);
+ }
+ }
+
+ void dispose() {
+ if (isInitialized) {
+ exoPlayer.stop();
+ }
+ textureEntry.release();
+ eventChannel.setStreamHandler(null);
+ if (surface != null) {
+ surface.release();
+ }
+ if (exoPlayer != null) {
+ exoPlayer.release();
+ }
+ }
+}
diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
index 5b1f55f..fdc0511 100644
--- a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
+++ b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
@@ -4,322 +4,70 @@
package io.flutter.plugins.videoplayer;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
-
import android.content.Context;
-import android.net.Uri;
-import android.os.Build;
+import android.util.Log;
import android.util.LongSparseArray;
-import android.view.Surface;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Player.EventListener;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.audio.AudioAttributes;
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-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.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
-import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
-import io.flutter.view.FlutterNativeView;
+import io.flutter.view.FlutterMain;
import io.flutter.view.TextureRegistry;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-public class VideoPlayerPlugin implements MethodCallHandler {
+/** Android platform implementation of the VideoPlayerPlugin. */
+public class VideoPlayerPlugin implements MethodCallHandler, FlutterPlugin {
+ private static final String TAG = "VideoPlayerPlugin";
+ private final LongSparseArray<VideoPlayer> videoPlayers = new LongSparseArray<>();
+ private FlutterState flutterState;
- private static class VideoPlayer {
- private static final String FORMAT_SS = "ss";
- private static final String FORMAT_DASH = "dash";
- private static final String FORMAT_HLS = "hls";
- private static final String FORMAT_OTHER = "other";
+ /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */
+ public VideoPlayerPlugin() {}
- private SimpleExoPlayer exoPlayer;
-
- private Surface surface;
-
- private final TextureRegistry.SurfaceTextureEntry textureEntry;
-
- private QueuingEventSink eventSink = new QueuingEventSink();
-
- private final EventChannel eventChannel;
-
- private boolean isInitialized = false;
-
- VideoPlayer(
- Context context,
- EventChannel eventChannel,
- TextureRegistry.SurfaceTextureEntry textureEntry,
- String dataSource,
- Result result,
- String formatHint) {
- this.eventChannel = eventChannel;
- this.textureEntry = textureEntry;
-
- TrackSelector trackSelector = new DefaultTrackSelector();
- exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
-
- Uri uri = Uri.parse(dataSource);
-
- DataSource.Factory dataSourceFactory;
- if (isHTTP(uri)) {
- dataSourceFactory =
- new DefaultHttpDataSourceFactory(
- "ExoPlayer",
- null,
- DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
- DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
- true);
- } else {
- dataSourceFactory = new DefaultDataSourceFactory(context, "ExoPlayer");
- }
-
- MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context);
- exoPlayer.prepare(mediaSource);
-
- setupVideoPlayer(eventChannel, textureEntry, result);
- }
-
- private static boolean isHTTP(Uri uri) {
- if (uri == null || uri.getScheme() == null) {
- return false;
- }
- String scheme = uri.getScheme();
- return scheme.equals("http") || scheme.equals("https");
- }
-
- private MediaSource buildMediaSource(
- Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) {
- int type;
- if (formatHint == null) {
- type = Util.inferContentType(uri.getLastPathSegment());
- } else {
- switch (formatHint) {
- case FORMAT_SS:
- type = C.TYPE_SS;
- break;
- case FORMAT_DASH:
- type = C.TYPE_DASH;
- break;
- case FORMAT_HLS:
- type = C.TYPE_HLS;
- break;
- case FORMAT_OTHER:
- type = C.TYPE_OTHER;
- break;
- default:
- type = -1;
- break;
- }
- }
- switch (type) {
- case C.TYPE_SS:
- return new SsMediaSource.Factory(
- new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
- new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
- .createMediaSource(uri);
- case C.TYPE_DASH:
- return new DashMediaSource.Factory(
- new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
- new DefaultDataSourceFactory(context, null, mediaDataSourceFactory))
- .createMediaSource(uri);
- case C.TYPE_HLS:
- return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
- case C.TYPE_OTHER:
- return new ExtractorMediaSource.Factory(mediaDataSourceFactory)
- .setExtractorsFactory(new DefaultExtractorsFactory())
- .createMediaSource(uri);
- default:
- {
- throw new IllegalStateException("Unsupported type: " + type);
- }
- }
- }
-
- private void setupVideoPlayer(
- EventChannel eventChannel,
- TextureRegistry.SurfaceTextureEntry textureEntry,
- Result result) {
-
- eventChannel.setStreamHandler(
- new EventChannel.StreamHandler() {
- @Override
- public void onListen(Object o, EventChannel.EventSink sink) {
- eventSink.setDelegate(sink);
- }
-
- @Override
- public void onCancel(Object o) {
- eventSink.setDelegate(null);
- }
- });
-
- surface = new Surface(textureEntry.surfaceTexture());
- exoPlayer.setVideoSurface(surface);
- setAudioAttributes(exoPlayer);
-
- exoPlayer.addListener(
- new EventListener() {
-
- @Override
- public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
- if (playbackState == Player.STATE_BUFFERING) {
- sendBufferingUpdate();
- } else if (playbackState == Player.STATE_READY) {
- if (!isInitialized) {
- isInitialized = true;
- sendInitialized();
- }
- } else if (playbackState == Player.STATE_ENDED) {
- Map<String, Object> event = new HashMap<>();
- event.put("event", "completed");
- eventSink.success(event);
- }
- }
-
- @Override
- public void onPlayerError(final ExoPlaybackException error) {
- if (eventSink != null) {
- eventSink.error("VideoError", "Video player had error " + error, null);
- }
- }
- });
-
- Map<String, Object> reply = new HashMap<>();
- reply.put("textureId", textureEntry.id());
- result.success(reply);
- }
-
- private void sendBufferingUpdate() {
- Map<String, Object> event = new HashMap<>();
- event.put("event", "bufferingUpdate");
- List<? extends Number> range = Arrays.asList(0, exoPlayer.getBufferedPosition());
- // iOS supports a list of buffered ranges, so here is a list with a single range.
- event.put("values", Collections.singletonList(range));
- eventSink.success(event);
- }
-
- @SuppressWarnings("deprecation")
- private static void setAudioAttributes(SimpleExoPlayer exoPlayer) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- exoPlayer.setAudioAttributes(
- new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MOVIE).build());
- } else {
- exoPlayer.setAudioStreamType(C.STREAM_TYPE_MUSIC);
- }
- }
-
- void play() {
- exoPlayer.setPlayWhenReady(true);
- }
-
- void pause() {
- exoPlayer.setPlayWhenReady(false);
- }
-
- void setLooping(boolean value) {
- exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF);
- }
-
- void setVolume(double value) {
- float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value));
- exoPlayer.setVolume(bracketedValue);
- }
-
- void seekTo(int location) {
- exoPlayer.seekTo(location);
- }
-
- long getPosition() {
- return exoPlayer.getCurrentPosition();
- }
-
- @SuppressWarnings("SuspiciousNameCombination")
- private void sendInitialized() {
- if (isInitialized) {
- Map<String, Object> event = new HashMap<>();
- event.put("event", "initialized");
- event.put("duration", exoPlayer.getDuration());
-
- if (exoPlayer.getVideoFormat() != null) {
- Format videoFormat = exoPlayer.getVideoFormat();
- int width = videoFormat.width;
- int height = videoFormat.height;
- int rotationDegrees = videoFormat.rotationDegrees;
- // Switch the width/height if video was taken in portrait mode
- if (rotationDegrees == 90 || rotationDegrees == 270) {
- width = exoPlayer.getVideoFormat().height;
- height = exoPlayer.getVideoFormat().width;
- }
- event.put("width", width);
- event.put("height", height);
- }
- eventSink.success(event);
- }
- }
-
- void dispose() {
- if (isInitialized) {
- exoPlayer.stop();
- }
- textureEntry.release();
- eventChannel.setStreamHandler(null);
- if (surface != null) {
- surface.release();
- }
- if (exoPlayer != null) {
- exoPlayer.release();
- }
- }
+ private VideoPlayerPlugin(Registrar registrar) {
+ this.flutterState =
+ new FlutterState(
+ registrar.context(),
+ registrar.messenger(),
+ registrar::lookupKeyForAsset,
+ registrar::lookupKeyForAsset,
+ registrar.textures());
+ flutterState.startListening(this);
}
+ /** Registers this with the stable v1 embedding. Will not respond to lifecycle events. */
public static void registerWith(Registrar registrar) {
final VideoPlayerPlugin plugin = new VideoPlayerPlugin(registrar);
- final MethodChannel channel =
- new MethodChannel(registrar.messenger(), "flutter.io/videoPlayer");
- channel.setMethodCallHandler(plugin);
registrar.addViewDestroyListener(
- new PluginRegistry.ViewDestroyListener() {
- @Override
- public boolean onViewDestroy(FlutterNativeView view) {
- plugin.onDestroy();
- return false; // We are not interested in assuming ownership of the NativeView.
- }
+ view -> {
+ plugin.onDestroy();
+ return false; // We are not interested in assuming ownership of the NativeView.
});
}
- private VideoPlayerPlugin(Registrar registrar) {
- this.registrar = registrar;
- this.videoPlayers = new LongSparseArray<>();
+ @Override
+ public void onAttachedToEngine(FlutterPluginBinding binding) {
+ this.flutterState =
+ new FlutterState(
+ binding.getApplicationContext(),
+ binding.getFlutterEngine().getDartExecutor(),
+ FlutterMain::getLookupKeyForAsset,
+ FlutterMain::getLookupKeyForAsset,
+ binding.getFlutterEngine().getRenderer());
+ flutterState.startListening(this);
}
- private final LongSparseArray<VideoPlayer> videoPlayers;
-
- private final Registrar registrar;
+ @Override
+ public void onDetachedFromEngine(FlutterPluginBinding binding) {
+ if (flutterState == null) {
+ Log.wtf(TAG, "Detached from the engine before registering to it.");
+ }
+ flutterState.stopListening();
+ flutterState = null;
+ }
private void disposeAllPlayers() {
for (int i = 0; i < videoPlayers.size(); i++) {
@@ -339,8 +87,7 @@
@Override
public void onMethodCall(MethodCall call, Result result) {
- TextureRegistry textures = registrar.textures();
- if (textures == null) {
+ if (flutterState == null || flutterState.textureRegistry == null) {
result.error("no_activity", "video_player plugin requires a foreground activity", null);
return;
}
@@ -350,23 +97,25 @@
break;
case "create":
{
- TextureRegistry.SurfaceTextureEntry handle = textures.createSurfaceTexture();
+ TextureRegistry.SurfaceTextureEntry handle =
+ flutterState.textureRegistry.createSurfaceTexture();
EventChannel eventChannel =
new EventChannel(
- registrar.messenger(), "flutter.io/videoPlayer/videoEvents" + handle.id());
+ flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id());
VideoPlayer player;
if (call.argument("asset") != null) {
String assetLookupKey;
if (call.argument("package") != null) {
assetLookupKey =
- registrar.lookupKeyForAsset(call.argument("asset"), call.argument("package"));
+ flutterState.keyForAssetAndPackageName.get(
+ call.argument("asset"), call.argument("package"));
} else {
- assetLookupKey = registrar.lookupKeyForAsset(call.argument("asset"));
+ assetLookupKey = flutterState.keyForAsset.get(call.argument("asset"));
}
player =
new VideoPlayer(
- registrar.context(),
+ flutterState.applicationContext,
eventChannel,
handle,
"asset:///" + assetLookupKey,
@@ -376,7 +125,7 @@
} else {
player =
new VideoPlayer(
- registrar.context(),
+ flutterState.applicationContext,
eventChannel,
handle,
call.argument("uri"),
@@ -440,4 +189,43 @@
break;
}
}
+
+ private interface KeyForAssetFn {
+ String get(String asset);
+ }
+
+ private interface KeyForAssetAndPackageName {
+ String get(String asset, String packageName);
+ }
+
+ private static final class FlutterState {
+ private final Context applicationContext;
+ private final BinaryMessenger binaryMessenger;
+ private final KeyForAssetFn keyForAsset;
+ private final KeyForAssetAndPackageName keyForAssetAndPackageName;
+ private final TextureRegistry textureRegistry;
+ private final MethodChannel methodChannel;
+
+ FlutterState(
+ Context applicationContext,
+ BinaryMessenger messenger,
+ KeyForAssetFn keyForAsset,
+ KeyForAssetAndPackageName keyForAssetAndPackageName,
+ TextureRegistry textureRegistry) {
+ this.applicationContext = applicationContext;
+ this.binaryMessenger = messenger;
+ this.keyForAsset = keyForAsset;
+ this.keyForAssetAndPackageName = keyForAssetAndPackageName;
+ this.textureRegistry = textureRegistry;
+ methodChannel = new MethodChannel(messenger, "flutter.io/videoPlayer");
+ }
+
+ void startListening(VideoPlayerPlugin methodCallHandler) {
+ methodChannel.setMethodCallHandler(methodCallHandler);
+ }
+
+ void stopListening() {
+ methodChannel.setMethodCallHandler(null);
+ }
+ }
}
diff --git a/packages/video_player/example/android/app/src/main/AndroidManifest.xml b/packages/video_player/example/android/app/src/main/AndroidManifest.xml
index 914e82b..deec4b6 100644
--- a/packages/video_player/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/video_player/example/android/app/src/main/AndroidManifest.xml
@@ -1,27 +1,35 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="io.flutter.plugins.videoplayerexample">
+ package="io.flutter.plugins.videoplayerexample">
- <uses-permission android:name="android.permission.INTERNET"/>
+ <application
+ android:icon="@mipmap/ic_launcher"
+ android:label="video_player_example"
+ android:name="io.flutter.app.FlutterApplication"
+ android:networkSecurityConfig="@xml/network_security_config">
+ <activity
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+ android:exported="true"
+ android:hardwareAccelerated="true"
+ android:launchMode="singleTop"
+ android:name=".EmbeddingV1Activity"
+ android:theme="@style/LaunchTheme"
+ android:windowSoftInputMode="adjustResize">
+ <meta-data
+ android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
+ android:value="true"/>
+ </activity>
+ <activity
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+ android:hardwareAccelerated="true"
+ android:name=".MainActivity"
+ android:theme="@style/LaunchTheme"
+ android:windowSoftInputMode="adjustResize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
- <application
- android:name="io.flutter.app.FlutterApplication"
- android:label="video_player_example"
- android:icon="@mipmap/ic_launcher"
- android:networkSecurityConfig="@xml/network_security_config">
- <activity
- android:name=".MainActivity"
- android:launchMode="singleTop"
- android:theme="@style/LaunchTheme"
- android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
- android:hardwareAccelerated="true"
- android:windowSoftInputMode="adjustResize">
- <meta-data
- android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
- android:value="true" />
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
+ <uses-permission android:name="android.permission.INTERNET"/>
</manifest>
diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java
new file mode 100644
index 0000000..f1af8ec
--- /dev/null
+++ b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.videoplayerexample;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+
+public class EmbeddingV1Activity extends FlutterActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ GeneratedPluginRegistrant.registerWith(this);
+ }
+}
diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java
index 133c3fa..2a0ae15 100644
--- a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java
+++ b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java
@@ -4,14 +4,13 @@
package io.flutter.plugins.videoplayerexample;
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.plugins.videoplayer.VideoPlayerPlugin;
public class MainActivity extends FlutterActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
+ public void configureFlutterEngine(FlutterEngine flutterEngine) {
+ flutterEngine.getPlugins().add(new VideoPlayerPlugin());
}
}
diff --git a/packages/video_player/example/android/gradle.properties b/packages/video_player/example/android/gradle.properties
index 8bd86f6..a673820 100644
--- a/packages/video_player/example/android/gradle.properties
+++ b/packages/video_player/example/android/gradle.properties
@@ -1 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart
index dea1086..196b908 100644
--- a/packages/video_player/example/lib/main.dart
+++ b/packages/video_player/example/lib/main.dart
@@ -388,11 +388,11 @@
Container(
padding: const EdgeInsets.only(top: 20.0),
),
- const Text('With remote m3u8'),
+ const Text('With remote mp4'),
Container(
padding: const EdgeInsets.all(20),
child: NetworkPlayerLifeCycle(
- 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8',
+ 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller),
diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/example/pubspec.yaml
index da48f06..bcb559b 100644
--- a/packages/video_player/example/pubspec.yaml
+++ b/packages/video_player/example/pubspec.yaml
@@ -8,7 +8,9 @@
dev_dependencies:
flutter_test:
sdk: flutter
-
+ flutter_driver:
+ sdk: flutter
+ e2e: "^0.2.0"
video_player:
path: ../
diff --git a/packages/video_player/example/test_driver/video_player_e2e.dart b/packages/video_player/example/test_driver/video_player_e2e.dart
new file mode 100644
index 0000000..e726ab2
--- /dev/null
+++ b/packages/video_player/example/test_driver/video_player_e2e.dart
@@ -0,0 +1,67 @@
+// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'package:e2e/e2e.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:video_player/video_player.dart';
+
+const Duration _playDuration = Duration(seconds: 1);
+
+void main() {
+ E2EWidgetsFlutterBinding.ensureInitialized();
+ VideoPlayerController _controller;
+
+ tearDown(() async => _controller.dispose());
+
+ group('asset videos', () {
+ setUp(() {
+ _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4');
+ });
+
+ testWidgets('can be initialized', (WidgetTester tester) async {
+ await _controller.initialize();
+
+ expect(_controller.value.initialized, true);
+ expect(_controller.value.position, const Duration(seconds: 0));
+ expect(_controller.value.isPlaying, false);
+ expect(_controller.value.duration,
+ const Duration(seconds: 7, milliseconds: 540));
+ });
+
+ testWidgets('can be played', (WidgetTester tester) async {
+ await _controller.initialize();
+
+ await _controller.play();
+ await tester.pumpAndSettle(_playDuration);
+
+ expect(_controller.value.isPlaying, true);
+ expect(_controller.value.position,
+ (Duration position) => position > const Duration(seconds: 0));
+ }, skip: Platform.isIOS);
+
+ testWidgets('can seek', (WidgetTester tester) async {
+ await _controller.initialize();
+
+ await _controller.seekTo(const Duration(seconds: 3));
+
+ expect(_controller.value.position, const Duration(seconds: 3));
+ }, skip: Platform.isIOS);
+
+ testWidgets('can be paused', (WidgetTester tester) async {
+ await _controller.initialize();
+
+ // Play for a second, then pause, and then wait a second.
+ await _controller.play();
+ await tester.pumpAndSettle(_playDuration);
+ await _controller.pause();
+ final Duration pausedPosition = _controller.value.position;
+ await tester.pumpAndSettle(_playDuration);
+
+ // Verify that we stopped playing after the pause.
+ expect(_controller.value.isPlaying, false);
+ expect(_controller.value.position, pausedPosition);
+ }, skip: Platform.isIOS);
+ });
+}
diff --git a/packages/video_player/example/test_driver/video_player_e2e_test.dart b/packages/video_player/example/test_driver/video_player_e2e_test.dart
new file mode 100644
index 0000000..ccd7166
--- /dev/null
+++ b/packages/video_player/example/test_driver/video_player_e2e_test.dart
@@ -0,0 +1,16 @@
+// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter_driver/flutter_driver.dart';
+
+Future<void> main() async {
+ final FlutterDriver driver = await FlutterDriver.connect();
+ final String result =
+ await driver.requestData(null, timeout: const Duration(minutes: 1));
+ await driver.close();
+ exit(result == 'pass' ? 0 : 1);
+}
diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml
index 1b0805b..656a81f 100644
--- a/packages/video_player/pubspec.yaml
+++ b/packages/video_player/pubspec.yaml
@@ -2,7 +2,7 @@
description: Flutter plugin for displaying inline video with other Flutter
widgets on Android and iOS.
author: Flutter Team <flutter-dev@googlegroups.com>
-version: 0.10.2+6
+version: 0.10.3
homepage: https://github.com/flutter/plugins/tree/master/packages/video_player
flutter:
@@ -22,4 +22,4 @@
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
- flutter: ">=1.5.0 <2.0.0"
+ flutter: ">=1.9.1+hotfix.5 <2.0.0"