diff --git a/README.md b/README.md index 33ea424e68c9..0fcbb5ccb7d1 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ These are the available plugins in this repository. | [share](./packages/share/) | [![pub package](https://img.shields.io/pub/v/share.svg)](https://pub.dartlang.org/packages/share) | | [shared_preferences](./packages/shared_preferences/) | [![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dartlang.org/packages/shared_preferences) | | [url_launcher](./packages/url_launcher/) | [![pub package](https://img.shields.io/pub/v/url_launcher.svg)](https://pub.dartlang.org/packages/url_launcher) | +| [video_player](./packages/video_player/) | [![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dartlang.org/packages/video_player) | | | | | **FlutterFire Plugins** | | | [firebase_analytics](./packages/firebase_analytics/) | [![pub package](https://img.shields.io/pub/v/firebase_analytics.svg)](https://pub.dartlang.org/packages/firebase_analytics) | diff --git a/packages/video_player/.gitignore b/packages/video_player/.gitignore new file mode 100644 index 000000000000..14c7d4c3f73e --- /dev/null +++ b/packages/video_player/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.atom/ +.idea +.packages +.pub/ +build/ +ios/.generated/ +packages +pubspec.lock diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md new file mode 100644 index 000000000000..4aca5378f68f --- /dev/null +++ b/packages/video_player/CHANGELOG.md @@ -0,0 +1,13 @@ +## 0.0.3 + +* Made creating a VideoPlayerController a synchronous operation. Must be followed by a call to initialize(). +* Added VideoPlayerController.setVolume(). +* Moved the package to flutter/plugins github repo. + +## 0.0.2 + +* Fix meta dependency version. + +## 0.0.1 + +* Initial release diff --git a/packages/video_player/LICENSE b/packages/video_player/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/video_player/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/video_player/README.md b/packages/video_player/README.md new file mode 100644 index 000000000000..6df2e65a4a75 --- /dev/null +++ b/packages/video_player/README.md @@ -0,0 +1,88 @@ +# Video Player plugin for Flutter + +[![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dartlang.org/packages/video_player) + +A Flutter plugin for iOS and Android for playing back video on a Widget surface. + +*Note*: This plugin is still under development, and some APIs might not be available yet. +[Feedback welcome](https://github.com/flutter/flutter/issues) and +[Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! + +## Installation + +First, add `video_player` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). + +### iOS + +Add the following entry to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: + +```xml +NSAppTransportSecurity + + NSAllowsArbitraryLoads + + +``` + +This entry allows your app to access video files by URL. + +### Android + +Ensure the following permission is present in your Android Manifest file, located in `/android/app/src/main/AndroidManifest.xml: + +```xml + +``` + +The Flutter project template adds it, so it may already be there. + +### Example + +```dart +class _MyHomePageState extends State { + VideoPlayerController _controller; + bool _isPlaying = false; + + @override + void initState() { + super.initState(); + _controller = new VideoPlayerController( + 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', + ) + ..addListener(() { + final bool isPlaying = _controller.value.isPlaying; + if (isPlaying != _isPlaying) { + setState(() { + _isPlaying = isPlaying; + }); + } + }) + ..initialize(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + appBar: new AppBar( + title: new Text(widget.title), + ), + body: new Center( + child: new Padding( + padding: const EdgeInsets.all(10.0), + child: new AspectRatio( + aspectRatio: 1280 / 720, + child: new VideoPlayer(_controller), + ), + ), + ), + floatingActionButton: new FloatingActionButton( + onPressed: + _controller.value.isPlaying ? _controller.pause : _controller.play, + child: new Icon( + _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, + ), + ), + ); + } +} +``` diff --git a/packages/video_player/android/.gitignore b/packages/video_player/android/.gitignore new file mode 100644 index 000000000000..c6cbe562a427 --- /dev/null +++ b/packages/video_player/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/packages/video_player/android/build.gradle b/packages/video_player/android/build.gradle new file mode 100644 index 000000000000..d77c6608a819 --- /dev/null +++ b/packages/video_player/android/build.gradle @@ -0,0 +1,42 @@ +group 'io.flutter.videoplayer' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + jcenter() + maven { + url "https://maven.google.com" + } + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + } +} + +rootProject.allprojects { + repositories { + jcenter() + maven { + url "https://maven.google.com" + } + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion '25.0.3' + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} diff --git a/packages/video_player/android/gradle.properties b/packages/video_player/android/gradle.properties new file mode 100644 index 000000000000..8bd86f680510 --- /dev/null +++ b/packages/video_player/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/video_player/android/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..45e7f14e952d --- /dev/null +++ b/packages/video_player/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/packages/video_player/android/settings.gradle b/packages/video_player/android/settings.gradle new file mode 100644 index 000000000000..bbc9b9dd21d8 --- /dev/null +++ b/packages/video_player/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'video_player' diff --git a/packages/video_player/android/src/main/AndroidManifest.xml b/packages/video_player/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..f29469ce4ebf --- /dev/null +++ b/packages/video_player/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/video_player/android/src/main/java/io/flutter/videoplayer/VideoPlayerPlugin.java b/packages/video_player/android/src/main/java/io/flutter/videoplayer/VideoPlayerPlugin.java new file mode 100644 index 000000000000..b91d08497b3d --- /dev/null +++ b/packages/video_player/android/src/main/java/io/flutter/videoplayer/VideoPlayerPlugin.java @@ -0,0 +1,236 @@ +// Copyright 2017 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.videoplayer; + +import android.annotation.TargetApi; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.Build; +import android.view.Surface; +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.Registrar; +import io.flutter.view.TextureRegistry; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VideoPlayerPlugin implements MethodCallHandler { + private class VideoPlayer { + private final TextureRegistry.SurfaceTextureEntry textureEntry; + private final MediaPlayer mediaPlayer; + private EventChannel.EventSink eventSink; + private final EventChannel eventChannel; + private boolean isPlaying = false; + private boolean isInitialized = false; + + @TargetApi(21) + VideoPlayer( + final EventChannel eventChannel, + final TextureRegistry.SurfaceTextureEntry textureEntry, + String dataSource, + final Result result) { + this.eventChannel = eventChannel; + eventChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink sink) { + eventSink = sink; + sendInitialized(); + } + + @Override + public void onCancel(Object o) { + eventSink = null; + } + }); + this.textureEntry = textureEntry; + this.mediaPlayer = new MediaPlayer(); + try { + mediaPlayer.setSurface(new Surface(textureEntry.surfaceTexture())); + mediaPlayer.setDataSource(dataSource); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + mediaPlayer.setAudioAttributes( + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .build()); + } else { + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + } + mediaPlayer.setOnPreparedListener( + new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mediaPlayer.setOnBufferingUpdateListener( + new MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(MediaPlayer mediaPlayer, int percent) { + if (eventSink != null) { + Map event = new HashMap<>(); + event.put("event", "bufferingUpdate"); + List range = + Arrays.asList(0, percent * mediaPlayer.getDuration() / 100); + // iOS supports a list of buffered ranges, so here is a list with a single range. + event.put("values", Arrays.asList(range)); + eventSink.success(event); + } + } + }); + isInitialized = true; + sendInitialized(); + } + }); + + mediaPlayer.setOnErrorListener( + new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + eventSink.error( + "VideoError", "Video player had error " + what + " extra " + extra, null); + return true; + } + }); + + mediaPlayer.setOnCompletionListener( + new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + Map event = new HashMap<>(); + event.put("event", "completed"); + eventSink.success(event); + } + }); + + mediaPlayer.prepareAsync(); + } catch (IOException e) { + result.error("VideoError", "IOError when initializing video player " + e.toString(), null); + } + Map reply = new HashMap<>(); + reply.put("textureId", textureEntry.id()); + result.success(reply); + } + + void play() { + if (!mediaPlayer.isPlaying()) { + mediaPlayer.start(); + } + } + + void pause() { + if (mediaPlayer.isPlaying()) { + mediaPlayer.pause(); + } + } + + void setLooping(boolean value) { + mediaPlayer.setLooping(value); + } + + void setVolume(double value) { + float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value)); + mediaPlayer.setVolume(bracketedValue, bracketedValue); + } + + void seekTo(int location) { + mediaPlayer.seekTo(location); + } + + int getPosition() { + return mediaPlayer.getCurrentPosition(); + } + + private void sendInitialized() { + if (isInitialized && eventSink != null) { + Map event = new HashMap<>(); + event.put("event", "initialized"); + event.put("duration", mediaPlayer.getDuration()); + eventSink.success(event); + } + } + + void dispose() { + if (isInitialized && mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } + mediaPlayer.reset(); + mediaPlayer.release(); + textureEntry.release(); + eventChannel.setStreamHandler(null); + } + } + + public static void registerWith(Registrar registrar) { + final MethodChannel channel = + new MethodChannel(registrar.messenger(), "flutter.io/videoPlayer"); + channel.setMethodCallHandler( + new VideoPlayerPlugin(registrar.messenger(), registrar.textures())); + } + + private VideoPlayerPlugin(BinaryMessenger messenger, TextureRegistry textures) { + this.textures = textures; + this.videoPlayers = new HashMap<>(); + this.messenger = messenger; + } + + private final Map videoPlayers; + private final TextureRegistry textures; + private final BinaryMessenger messenger; + + @Override + public void onMethodCall(MethodCall call, Result result) { + if (call.method.equals("init")) { + for (VideoPlayer player : videoPlayers.values()) { + player.dispose(); + } + videoPlayers.clear(); + } else if (call.method.equals("create")) { + TextureRegistry.SurfaceTextureEntry handle = textures.createSurfaceTexture(); + EventChannel eventChannel = + new EventChannel(messenger, "flutter.io/videoPlayer/videoEvents" + handle.id()); + VideoPlayer player = + new VideoPlayer(eventChannel, handle, (String) call.argument("dataSource"), result); + videoPlayers.put(handle.id(), player); + } else { + long textureId = ((Number) call.argument("textureId")).longValue(); + VideoPlayer player = videoPlayers.get(textureId); + if (player == null) { + result.error( + "Unknown textureId", "No video player associated with texture id " + textureId, null); + return; + } + if (call.method.equals("setLooping")) { + player.setLooping((Boolean) call.argument("looping")); + result.success(null); + } else if (call.method.equals("setVolume")) { + player.setVolume((Double) call.argument("volume")); + result.success(null); + } else if (call.method.equals("play")) { + player.play(); + result.success(null); + } else if (call.method.equals("pause")) { + player.pause(); + result.success(null); + } else if (call.method.equals("seekTo")) { + int location = ((Number) call.argument("location")).intValue(); + player.seekTo(location); + result.success(null); + } else if (call.method.equals("position")) { + result.success(player.getPosition()); + } else if (call.method.equals("dispose")) { + player.dispose(); + result.success(null); + } else { + result.notImplemented(); + } + } + } +} diff --git a/packages/video_player/example/.gitignore b/packages/video_player/example/.gitignore new file mode 100644 index 000000000000..eb15c3d27cab --- /dev/null +++ b/packages/video_player/example/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.atom/ +.idea +.packages +.pub/ +build/ +ios/.generated/ +packages +pubspec.lock +.flutter-plugins diff --git a/packages/video_player/example/README.md b/packages/video_player/example/README.md new file mode 100644 index 000000000000..55b086b4f33f --- /dev/null +++ b/packages/video_player/example/README.md @@ -0,0 +1,8 @@ +# video_player_example + +Demonstrates how to use the video_player plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/video_player/example/android.iml b/packages/video_player/example/android.iml new file mode 100644 index 000000000000..462b903e05b6 --- /dev/null +++ b/packages/video_player/example/android.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/video_player/example/android/.gitignore b/packages/video_player/example/android/.gitignore new file mode 100644 index 000000000000..1658458c9245 --- /dev/null +++ b/packages/video_player/example/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +GeneratedPluginRegistrant.java diff --git a/packages/video_player/example/android/app/build.gradle b/packages/video_player/example/android/app/build.gradle new file mode 100644 index 000000000000..84b482098335 --- /dev/null +++ b/packages/video_player/example/android/app/build.gradle @@ -0,0 +1,52 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { stream -> + localProperties.load(stream) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 25 + buildToolsVersion '25.0.3' + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.flutter.videoplayerexample" + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + androidTestCompile 'com.android.support:support-annotations:25.4.0' + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' +} diff --git a/packages/video_player/example/android/app/src/main/AndroidManifest.xml b/packages/video_player/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..3a161c58d06c --- /dev/null +++ b/packages/video_player/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/videoplayerexample/MainActivity.java b/packages/video_player/example/android/app/src/main/java/io/flutter/videoplayerexample/MainActivity.java new file mode 100644 index 000000000000..16551599effb --- /dev/null +++ b/packages/video_player/example/android/app/src/main/java/io/flutter/videoplayerexample/MainActivity.java @@ -0,0 +1,17 @@ +// Copyright 2017 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.videoplayerexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity 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/res/drawable/launch_background.xml b/packages/video_player/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/video_player/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..09d4391482be Binary files /dev/null and b/packages/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/video_player/example/android/app/src/main/res/values/styles.xml b/packages/video_player/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..5691c756a6bc --- /dev/null +++ b/packages/video_player/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + diff --git a/packages/video_player/example/android/build.gradle b/packages/video_player/example/android/build.gradle new file mode 100644 index 000000000000..77cbd091409d --- /dev/null +++ b/packages/video_player/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + repositories { + jcenter() + maven { + url "https://maven.google.com" + } + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + } +} + +allprojects { + repositories { + jcenter() + maven { + url "https://maven.google.com" + } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/video_player/example/android/gradle.properties b/packages/video_player/example/android/gradle.properties new file mode 100644 index 000000000000..8bd86f680510 --- /dev/null +++ b/packages/video_player/example/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/video_player/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..45e7f14e952d --- /dev/null +++ b/packages/video_player/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/packages/video_player/example/android/settings.gradle b/packages/video_player/example/android/settings.gradle new file mode 100644 index 000000000000..115da6cb4f4d --- /dev/null +++ b/packages/video_player/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/video_player/example/assets/flutter-mark-square-64.png b/packages/video_player/example/assets/flutter-mark-square-64.png new file mode 100644 index 000000000000..56f22d5bd8f4 Binary files /dev/null and b/packages/video_player/example/assets/flutter-mark-square-64.png differ diff --git a/packages/video_player/example/ios/.gitignore b/packages/video_player/example/ios/.gitignore new file mode 100644 index 000000000000..38864eed23ef --- /dev/null +++ b/packages/video_player/example/ios/.gitignore @@ -0,0 +1,41 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/app.flx +/Flutter/app.zip +/Flutter/App.framework +/Flutter/Flutter.framework +/Flutter/Generated.xcconfig +/ServiceDefinitions.json + +Pods/ diff --git a/packages/video_player/example/ios/Flutter/AppFrameworkInfo.plist b/packages/video_player/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000000..6c2de8086bcd --- /dev/null +++ b/packages/video_player/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/packages/video_player/example/ios/Flutter/Debug.xcconfig b/packages/video_player/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..592ceee85b89 --- /dev/null +++ b/packages/video_player/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/video_player/example/ios/Flutter/Release.xcconfig b/packages/video_player/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..592ceee85b89 --- /dev/null +++ b/packages/video_player/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/video_player/example/ios/Podfile b/packages/video_player/example/ios/Podfile new file mode 100644 index 000000000000..90b5f651fb63 --- /dev/null +++ b/packages/video_player/example/ios/Podfile @@ -0,0 +1,36 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +if ENV['FLUTTER_FRAMEWORK_DIR'] == nil + abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') +end + +target 'Runner' do + # Pods for Runner + + # Flutter Pods + pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] + + if File.exists? '../.flutter-plugins' + flutter_root = File.expand_path('..') + File.foreach('../.flutter-plugins') { |line| + plugin = line.split(pattern='=') + if plugin.length == 2 + name = plugin[0].strip() + path = plugin[1].strip() + resolved_path = File.expand_path("#{path}/ios", flutter_root) + pod name, :path => resolved_path + else + puts "Invalid plugin specification: #{line}" + end + } + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/video_player/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..b4ead2073c35 --- /dev/null +++ b/packages/video_player/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,491 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20721C28387E1F78689EC502 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 20721C28387E1F78689EC502 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E898481BC29A7FA83AA441 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + 23104BB9DCF267F65AD246F9 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 20721C28387E1F78689EC502 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 9740EEB71CF902C7004384FC /* app.flx */, + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 05E898481BC29A7FA83AA441 /* Pods */, + 23104BB9DCF267F65AD246F9 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 929A04F81CC936396BFCB39E /* [CP] Embed Pods Frameworks */, + F9EA30D8C9F7B021C29C3000 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9740EEBB1CF902C7004384FC /* app.flx in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 929A04F81CC936396BFCB39E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F9EA30D8C9F7B021C29C3000 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.videoPlayerExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.videoPlayerExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..1d526a16ed0f --- /dev/null +++ b/packages/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..1c9580788197 --- /dev/null +++ b/packages/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/video_player/example/ios/Runner/AppDelegate.h b/packages/video_player/example/ios/Runner/AppDelegate.h new file mode 100644 index 000000000000..d9e18e990f2e --- /dev/null +++ b/packages/video_player/example/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// Copyright 2017 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. + +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/video_player/example/ios/Runner/AppDelegate.m b/packages/video_player/example/ios/Runner/AppDelegate.m new file mode 100644 index 000000000000..f08675707182 --- /dev/null +++ b/packages/video_player/example/ios/Runner/AppDelegate.m @@ -0,0 +1,17 @@ +// Copyright 2017 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. + +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..d22f10b2ab63 --- /dev/null +++ b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000000..28c6bf03016f Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000000..f091b6b0bca8 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000000..4cde12118dda Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000000..d0ef06e7edb8 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000000..dcdc2306c285 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000000..c8f9ed8f5cee Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000000..75b2d164a5a9 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000000..c4df70d39da7 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000000..6a84f41e14e2 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000000..d0e1f5853602 Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000..f2e259c7c939 --- /dev/null +++ b/packages/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/video_player/example/ios/Runner/Base.lproj/Main.storyboard b/packages/video_player/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/video_player/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/video_player/example/ios/Runner/Info.plist b/packages/video_player/example/ios/Runner/Info.plist new file mode 100644 index 000000000000..51c931a5aa5c --- /dev/null +++ b/packages/video_player/example/ios/Runner/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + video_player_example + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/packages/video_player/example/ios/Runner/main.m b/packages/video_player/example/ios/Runner/main.m new file mode 100644 index 000000000000..bec320c0bee0 --- /dev/null +++ b/packages/video_player/example/ios/Runner/main.m @@ -0,0 +1,13 @@ +// Copyright 2017 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. + +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart new file mode 100644 index 000000000000..c709ce3ff0db --- /dev/null +++ b/packages/video_player/example/lib/main.dart @@ -0,0 +1,333 @@ +// Copyright 2017 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. + +/// 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'; + +/// Controls play and pause of [controller]. +/// +/// Toggles play/pause on tap (accompanied by a fading status icon). +/// +/// Plays (looping) on initialization, and mutes on deactivation. +class VideoPlayPause extends StatefulWidget { + final VideoPlayerController controller; + + VideoPlayPause(this.controller); + + @override + State createState() { + return new _VideoPlayPauseState(); + } +} + +class _VideoPlayPauseState extends State { + FadeAnimation imageFadeAnim = + new FadeAnimation(child: new Icon(Icons.play_arrow, size: 100.0)); + VoidCallback listener; + + _VideoPlayPauseState() { + listener = () { + setState(() {}); + }; + } + + VideoPlayerController get controller => widget.controller; + + @override + void initState() { + super.initState(); + controller.addListener(listener); + controller.setVolume(1.0); + controller.play(); + } + + @override + void deactivate() { + controller.setVolume(0.0); + controller.removeListener(listener); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + final List children = [ + new GestureDetector( + child: new VideoPlayer(controller), + onTap: () { + if (!controller.value.initialized) { + return; + } + if (controller.value.isPlaying) { + imageFadeAnim = + new FadeAnimation(child: new Icon(Icons.pause, size: 100.0)); + controller.pause(); + } else { + imageFadeAnim = new FadeAnimation( + child: new Icon(Icons.play_arrow, size: 100.0)); + controller.play(); + } + }, + ), + new Align( + alignment: Alignment.bottomCenter, + child: new SizedBox( + height: 20.0, + width: double.INFINITY, + child: new VideoProgressBar(controller), + ), + ), + new Center(child: imageFadeAnim), + ]; + + if (!controller.value.initialized) { + children.add(new Center(child: const CupertinoActivityIndicator())); + } + + return new Stack( + alignment: Alignment.bottomCenter, + fit: StackFit.passthrough, + children: children, + ); + } +} + +class FadeAnimation extends StatefulWidget { + final Widget child; + final Duration duration; + + FadeAnimation({this.child, this.duration: const Duration(milliseconds: 500)}); + + @override + _FadeAnimationState createState() => new _FadeAnimationState(); +} + +class _FadeAnimationState extends State + with SingleTickerProviderStateMixin { + AnimationController animationController; + + @override + void initState() { + super.initState(); + animationController = + new AnimationController(duration: widget.duration, vsync: this); + animationController.addListener(() { + if (mounted) { + setState(() {}); + } + }); + animationController.forward(from: 0.0); + } + + @override + void deactivate() { + animationController.stop(); + super.deactivate(); + } + + @override + void didUpdateWidget(FadeAnimation oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.child != widget.child) { + animationController.forward(from: 0.0); + } + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return animationController.isAnimating + ? new Opacity( + opacity: 1.0 - animationController.value, + child: widget.child, + ) + : new Container(); + } +} + +typedef Widget VideoWidgetBuilder( + BuildContext context, VideoPlayerController controller); + +/// A widget connecting its life cycle to a [VideoPlayerController]. +class PlayerLifeCycle extends StatefulWidget { + final VideoWidgetBuilder childBuilder; + final String uri; + + PlayerLifeCycle(this.uri, this.childBuilder); + + @override + _PlayerLifeCycleState createState() => new _PlayerLifeCycleState(); +} + +class _PlayerLifeCycleState extends State { + VideoPlayerController controller; + + _PlayerLifeCycleState(); + + @override + void initState() { + super.initState(); + controller = new VideoPlayerController(widget.uri); + controller.addListener(() { + if (controller.value.isErroneous) { + print(controller.value.errorDescription); + } + }); + controller.initialize(); + controller.setLooping(true); + controller.play(); + } + + @override + void deactivate() { + super.deactivate(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.childBuilder(context, controller); + } +} + +/// A filler card to show the video in a list of scrolling contents. +Widget buildCard(String title) { + return new Card( + child: new Column( + mainAxisSize: MainAxisSize.min, + children: [ + new ListTile( + leading: const Icon(Icons.airline_seat_flat_angled), + title: new Text(title), + ), + new ButtonTheme.bar( + child: new ButtonBar( + children: [ + new FlatButton( + child: const Text('BUY TICKETS'), + onPressed: () {/* ... */}, + ), + new FlatButton( + child: const Text('SELL TICKETS'), + onPressed: () {/* ... */}, + ), + ], + ), + ), + ], + ), + ); +} + +class VideoInListOfCards extends StatelessWidget { + final VideoPlayerController controller; + + VideoInListOfCards(this.controller); + + @override + Widget build(BuildContext context) { + return new ListView( + children: [ + buildCard("Item a"), + buildCard("Item b"), + buildCard("Item c"), + buildCard("Item d"), + buildCard("Item e"), + buildCard("Item f"), + buildCard("Item g"), + new Card( + child: new Column(children: [ + new Column( + children: [ + const ListTile( + leading: const Icon(Icons.cake), + title: const Text("Video video"), + ), + new Stack( + alignment: FractionalOffset.bottomRight + + const FractionalOffset(-0.1, -0.1), + children: [ + new Center( + child: new AspectRatio( + aspectRatio: 3 / 2, + child: new VideoPlayPause(controller), + )), + new Image.asset('assets/flutter-mark-square-64.png'), + ]), + ], + ), + ])), + buildCard("Item h"), + buildCard("Item i"), + buildCard("Item j"), + buildCard("Item k"), + buildCard("Item l"), + ], + ); + } +} + +class FullScreenVideo extends StatelessWidget { + final VideoPlayerController controller; + + FullScreenVideo(this.controller); + + @override + Widget build(BuildContext context) { + return new Center( + child: new AspectRatio( + aspectRatio: 3 / 2, + child: new VideoPlayPause(controller), + ), + ); + } +} + +void main() { + runApp( + new MaterialApp( + home: new DefaultTabController( + length: 2, + child: new Scaffold( + appBar: new AppBar( + title: const Text('Video player example'), + bottom: new TabBar( + isScrollable: true, + tabs: [ + new Tab(icon: new Icon(Icons.fullscreen)), + new Tab(icon: new Icon(Icons.list)), + ], + ), + ), + body: new TabBarView( + children: [ + new PlayerLifeCycle( + 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', + (BuildContext context, VideoPlayerController controller) => + new FullScreenVideo(controller), + ), + new PlayerLifeCycle( + 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', + (BuildContext context, VideoPlayerController controller) => + new VideoInListOfCards(controller)), + ], + ), + ), + ), + ), + ); +} diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/example/pubspec.yaml new file mode 100644 index 000000000000..02f10573c069 --- /dev/null +++ b/packages/video_player/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: video_player_example +description: Demonstrates how to use the video_player plugin. + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + + video_player: + path: ../ + +flutter: + uses-material-design: true + assets: + - assets/flutter-mark-square-64.png diff --git a/packages/video_player/example/video_player_example.iml b/packages/video_player/example/video_player_example.iml new file mode 100644 index 000000000000..dafb001137cd --- /dev/null +++ b/packages/video_player/example/video_player_example.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/video_player/example/video_player_example_android.iml b/packages/video_player/example/video_player_example_android.iml new file mode 100644 index 000000000000..462b903e05b6 --- /dev/null +++ b/packages/video_player/example/video_player_example_android.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/video_player/ios/.gitignore b/packages/video_player/ios/.gitignore new file mode 100644 index 000000000000..956c87f3aa28 --- /dev/null +++ b/packages/video_player/ios/.gitignore @@ -0,0 +1,31 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + diff --git a/packages/video_player/ios/Assets/.gitkeep b/packages/video_player/ios/Assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h new file mode 100644 index 000000000000..d17f39868c8f --- /dev/null +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -0,0 +1,8 @@ +// Copyright 2017 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. + +#import + +@interface VideoPlayerPlugin : NSObject +@end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m new file mode 100644 index 000000000000..65745b72aaee --- /dev/null +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -0,0 +1,328 @@ +// Copyright 2017 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. + +#import "VideoPlayerPlugin.h" +#import + +int64_t CMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; } + +@interface FrameUpdater : NSObject +@property(nonatomic) int64_t textureId; +@property(nonatomic, readonly) NSObject* registry; +- (void)onDisplayLink:(CADisplayLink*)link; +@end + +@implementation FrameUpdater +- (FrameUpdater*)initWithRegistry:(NSObject*)registry { + NSAssert(self, @"super init cannot be nil"); + if (self == nil) return nil; + _registry = registry; + return self; +} + +- (void)onDisplayLink:(CADisplayLink*)link { + [_registry textureFrameAvailable:_textureId]; +} +@end + +@interface VideoPlayer : NSObject +@property(readonly, nonatomic) AVPlayer* player; +@property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; +@property(readonly, nonatomic) CADisplayLink* displayLink; +@property(nonatomic) FlutterEventChannel* eventChannel; +@property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic, readonly) bool disposed; +@property(nonatomic, readonly) bool isPlaying; +@property(nonatomic, readonly) bool isLooping; +@property(nonatomic, readonly) bool isInitialized; +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FrameUpdater*)frameUpdater; +- (void)play; +- (void)pause; +- (void)setIsLooping:(bool)isLooping; +- (void)updatePlayingState; +@end + +static void* timeRangeContext = &timeRangeContext; +static void* statusContext = &statusContext; +static void* playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; + +@implementation VideoPlayer +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FrameUpdater*)frameUpdater { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _isInitialized = false; + _isPlaying = false; + _disposed = false; + _player = [[AVPlayer alloc] init]; + _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification + object:[_player currentItem] + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* note) { + if (_isLooping) { + AVPlayerItem* p = [note object]; + [p seekToTime:kCMTimeZero]; + } else { + if (_eventSink) { + _eventSink(@{@"event" : @"completed"}); + } + } + }]; + NSDictionary* pixBuffAttributes = @{ + (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), + (id)kCVPixelBufferIOSurfacePropertiesKey : @{} + }; + _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; + AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; + + [item addObserver:self + forKeyPath:@"loadedTimeRanges" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:timeRangeContext]; + [item addObserver:self + forKeyPath:@"status" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:statusContext]; + [item addObserver:self + forKeyPath:@"playbackLikelyToKeepUp" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:playbackLikelyToKeepUpContext]; + + AVAsset* asset = [item asset]; + void (^assetCompletionHandler)(void) = ^{ + if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { + NSArray* tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count] > 0) { + AVAssetTrack* videoTrack = [tracks objectAtIndex:0]; + void (^trackCompletionHandler)(void) = ^{ + if (_disposed) return; + if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == + AVKeyValueStatusLoaded) { + dispatch_async(dispatch_get_main_queue(), ^{ + [_player replaceCurrentItemWithPlayerItem:item]; + }); + } + }; + [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] + completionHandler:trackCompletionHandler]; + } + } + }; + [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; + _displayLink = + [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + _displayLink.paused = YES; + return self; +} + +- (void)observeValueForKeyPath:(NSString*)path + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context { + if (context == timeRangeContext) { + if (_eventSink != nil) { + NSMutableArray*>* values = [[NSMutableArray alloc] init]; + for (NSValue* rangeValue in [object loadedTimeRanges]) { + CMTimeRange range = [rangeValue CMTimeRangeValue]; + int64_t start = CMTimeToMillis(range.start); + [values addObject:@[ @(start), @(start + CMTimeToMillis(range.duration)) ]]; + } + _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); + } + } else if (context == statusContext) { + if (_eventSink != nil) { + AVPlayerItem* item = (AVPlayerItem*)object; + switch (item.status) { + case AVPlayerStatusFailed: + _eventSink([FlutterError + errorWithCode:@"VideoError" + message:[@"Failed to load video: " + stringByAppendingString:[item.error localizedDescription]] + details:nil]); + break; + case AVPlayerItemStatusUnknown: + break; + case AVPlayerItemStatusReadyToPlay: + _isInitialized = true; + [item addOutput:_videoOutput]; + [self sendInitialized]; + [self updatePlayingState]; + break; + } + } + } else if (context == playbackLikelyToKeepUpContext) { + if ([[_player currentItem] isPlaybackLikelyToKeepUp]) { + [self updatePlayingState]; + } + } +} + +- (void)updatePlayingState { + if (!_isInitialized) { + return; + } + if (_isPlaying) { + [_player play]; + } else { + [_player pause]; + } + _displayLink.paused = !_isPlaying; +} + +- (void)sendInitialized { + if (_eventSink && _isInitialized) { + _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]) }); + } +} + +- (void)play { + _isPlaying = true; + [self updatePlayingState]; +} + +- (void)pause { + _isPlaying = false; + [self updatePlayingState]; +} + +- (int64_t)position { + return CMTimeToMillis([_player currentTime]); +} + +- (int64_t)duration { + return CMTimeToMillis([[_player currentItem] duration]); +} + +- (void)seekTo:(int)location { + [_player seekToTime:CMTimeMake(location, 1000)]; +} + +- (void)setIsLooping:(bool)isLooping { + _isLooping = isLooping; +} + +- (void)setVolume:(double)volume { + _player.volume = (volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume); +} + +- (CVPixelBufferRef)copyPixelBuffer { + CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; + if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { + return [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; + } else { + return NULL; + } +} + +- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { + _eventSink = nil; + return nil; +} + +- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments + eventSink:(nonnull FlutterEventSink)events { + _eventSink = events; + [self sendInitialized]; + return nil; +} + +- (void)dispose { + _disposed = true; + [_displayLink invalidate]; + [[_player currentItem] removeObserver:self forKeyPath:@"status" context:statusContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"loadedTimeRanges" + context:timeRangeContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"playbackLikelyToKeepUp" + context:playbackLikelyToKeepUpContext]; + [_player replaceCurrentItemWithPlayerItem:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_eventChannel setStreamHandler:nil]; +} + +@end + +@interface VideoPlayerPlugin () +@property(readonly, nonatomic) NSObject* registry; +@property(readonly, nonatomic) NSObject* messenger; +@property(readonly, nonatomic) NSMutableDictionary* players; +@end + +@implementation VideoPlayerPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = + [FlutterMethodChannel methodChannelWithName:@"flutter.io/videoPlayer" + binaryMessenger:[registrar messenger]]; + VideoPlayerPlugin* instance = [[VideoPlayerPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (instancetype)initWithRegistry:(NSObject*)registry + messenger:(NSObject*)messenger { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = registry; + _messenger = messenger; + _players = [NSMutableDictionary dictionaryWithCapacity:1]; + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"init" isEqualToString:call.method]) { + for (NSNumber* textureId in _players) { + [_registry unregisterTexture:[textureId unsignedIntegerValue]]; + [[_players objectForKey:textureId] dispose]; + } + [_players removeAllObjects]; + } else if ([@"create" isEqualToString:call.method]) { + NSDictionary* argsMap = call.arguments; + NSString* dataSource = argsMap[@"dataSource"]; + FrameUpdater* frameUpdater = [[FrameUpdater alloc] initWithRegistry:_registry]; + VideoPlayer* player = [[VideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] + frameUpdater:frameUpdater]; + int64_t textureId = [_registry registerTexture:player]; + frameUpdater.textureId = textureId; + FlutterEventChannel* eventChannel = [FlutterEventChannel + eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", + textureId] + binaryMessenger:_messenger]; + [eventChannel setStreamHandler:player]; + player.eventChannel = eventChannel; + _players[@(textureId)] = player; + result(@{ @"textureId" : @(textureId) }); + } else { + NSDictionary* argsMap = call.arguments; + int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; + VideoPlayer* player = _players[@(textureId)]; + if ([@"dispose" isEqualToString:call.method]) { + [_registry unregisterTexture:textureId]; + [_players removeObjectForKey:@(textureId)]; + [player dispose]; + } else if ([@"setLooping" isEqualToString:call.method]) { + [player setIsLooping:[argsMap objectForKey:@"looping"]]; + result(nil); + } else if ([@"setVolume" isEqualToString:call.method]) { + [player setVolume:[[argsMap objectForKey:@"volume"] doubleValue]]; + result(nil); + } else if ([@"play" isEqualToString:call.method]) { + [player play]; + result(nil); + } else if ([@"position" isEqualToString:call.method]) { + result(@([player position])); + } else if ([@"seekTo" isEqualToString:call.method]) { + [player seekTo:[[argsMap objectForKey:@"location"] intValue]]; + result(nil); + } else if ([@"pause" isEqualToString:call.method]) { + [player pause]; + result(nil); + } else { + result(FlutterMethodNotImplemented); + } + } +} + +@end diff --git a/packages/video_player/ios/video_player.podspec b/packages/video_player/ios/video_player.podspec new file mode 100644 index 000000000000..0c817478f39f --- /dev/null +++ b/packages/video_player/ios/video_player.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'video_player' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end + diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart new file mode 100644 index 000000000000..de1efe2d25bd --- /dev/null +++ b/packages/video_player/lib/video_player.dart @@ -0,0 +1,462 @@ +// Copyright 2017 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. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; + +final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer') + // This will clear all open videos on the platform when a full restart is + // performed. + ..invokeMethod("init"); + +class DurationRange { + DurationRange(this.start, this.end); + + final Duration start; + final Duration end; + + double startFraction(Duration duration) { + return start.inMilliseconds / duration.inMilliseconds; + } + + double endFraction(Duration duration) { + return end.inMilliseconds / duration.inMilliseconds; + } +} + +class VideoPlayerValue { + final Duration duration; + final Duration position; + final List buffered; + final bool isPlaying; + final bool isLooping; + final double volume; + final String errorDescription; + + VideoPlayerValue({ + @required this.duration, + this.position: const Duration(), + this.buffered: const [], + this.isPlaying: false, + this.isLooping: false, + this.volume: 1.0, + this.errorDescription, + }); + + VideoPlayerValue.uninitialized() : this(duration: null); + + VideoPlayerValue.erroneous(String errorDescription) + : this(duration: null, errorDescription: errorDescription); + + bool get initialized => duration != null; + bool get isErroneous => errorDescription != null; + + VideoPlayerValue copyWith({ + Duration duration, + Duration position, + List buffered, + bool isPlaying, + bool isLooping, + double volume, + String errorDescription, + }) { + return new VideoPlayerValue( + duration: duration ?? this.duration, + position: position ?? this.position, + buffered: buffered ?? this.buffered, + isPlaying: isPlaying ?? this.isPlaying, + isLooping: isLooping ?? this.isLooping, + volume: volume ?? this.volume, + errorDescription: errorDescription ?? this.errorDescription, + ); + } +} + +/// Controls a platform video player, and provides updates when the state is +/// changing. +/// +/// Instances must be initialized with initialize. +/// +/// The video is displayed in a Flutter app by creating a [VideoPlayer] widget. +/// +/// To reclaim the resources used by the player call [dispose]. +/// +/// After [dispose] all further calls are ignored. +class VideoPlayerController extends ValueNotifier { + int _textureId; + final String uri; + Timer timer; + bool isDisposed = false; + Completer _creatingCompleter; + StreamSubscription> _eventSubscription; + _VideoAppLifeCycleObserver _lifeCycleObserver; + + VideoPlayerController(this.uri) : super(new VideoPlayerValue(duration: null)); + + Future initialize() async { + _lifeCycleObserver = new _VideoAppLifeCycleObserver(this); + _lifeCycleObserver.initialize(); + _creatingCompleter = new Completer(); + final Map response = await _channel.invokeMethod( + 'create', + {'dataSource': uri}, + ); + _textureId = response["textureId"]; + _creatingCompleter.complete(null); + + DurationRange toDurationRange(List values) { + return new DurationRange( + new Duration(milliseconds: values[0]), + new Duration(milliseconds: values[1]), + ); + } + + void eventListener(Map event) { + if (event["event"] == "initialized") { + value = value.copyWith( + duration: new Duration(milliseconds: event["duration"]), + ); + _applyLooping(); + _applyVolume(); + _applyPlayPause(); + } else if (event["event"] == "completed") { + value = value.copyWith(isPlaying: false); + timer?.cancel(); + } else if (event["event"] == "bufferingUpdate") { + final List> bufferedValues = event["values"]; + value = value.copyWith( + buffered: bufferedValues.map(toDurationRange).toList(), + ); + } + } + + void errorListener(PlatformException e) { + value = new VideoPlayerValue.erroneous(e.message); + timer?.cancel(); + } + + _eventSubscription = _eventChannelFor(_textureId) + .receiveBroadcastStream() + .listen(eventListener, onError: errorListener); + } + + EventChannel _eventChannelFor(int textureId) { + return new EventChannel("flutter.io/videoPlayer/videoEvents$textureId"); + } + + @override + Future dispose() async { + if (_creatingCompleter != null) { + await _creatingCompleter.future; + if (!isDisposed) { + timer?.cancel(); + await _eventSubscription?.cancel(); + await _channel.invokeMethod( + 'dispose', + {'textureId': _textureId}, + ); + } + } + isDisposed = true; + _lifeCycleObserver.dispose(); + super.dispose(); + } + + Future play() async { + value = value.copyWith(isPlaying: true); + await _applyPlayPause(); + } + + Future setLooping(bool looping) async { + value = value.copyWith(isLooping: looping); + await _applyLooping(); + } + + Future pause() async { + value = value.copyWith(isPlaying: false); + await _applyPlayPause(); + } + + Future _applyLooping() async { + if (!value.initialized || isDisposed) { + return; + } + _channel.invokeMethod( + 'setLooping', + {'textureId': _textureId, 'looping': value.isLooping}, + ); + } + + Future _applyPlayPause() async { + if (!value.initialized || isDisposed) { + return; + } + if (value.isPlaying) { + await _channel.invokeMethod( + 'play', + {'textureId': _textureId}, + ); + timer = new Timer.periodic( + const Duration(milliseconds: 500), + (Timer timer) async { + final Duration newPosition = await position; + value = value.copyWith(position: newPosition); + }, + ); + } else { + timer?.cancel(); + await _channel.invokeMethod( + 'pause', + {'textureId': _textureId}, + ); + } + } + + Future _applyVolume() async { + if (!value.initialized || isDisposed) { + return; + } + await _channel.invokeMethod( + 'setVolume', + {'textureId': _textureId, 'volume': value.volume}, + ); + } + + /// The position in the current video. + Future get position async { + if (isDisposed) { + return null; + } + return new Duration( + milliseconds: await _channel.invokeMethod( + 'position', + {'textureId': _textureId}, + ), + ); + } + + Future seekTo(Duration moment) async { + if (isDisposed) { + return; + } + if (moment > value.duration) { + moment = value.duration; + } else if (moment < const Duration()) { + moment = const Duration(); + } + await _channel.invokeMethod('seekTo', { + 'textureId': _textureId, + 'location': moment.inMilliseconds, + }); + value = value.copyWith(position: moment); + } + + /// Sets the audio volume of [this]. + /// + /// [volume] indicates a value between 0.0 (silent) and 1.0 (full volume) on a + /// linear scale. + Future setVolume(double volume) async { + value = value.copyWith(volume: volume.clamp(0.0, 1.0)); + await _applyVolume(); + } +} + +class _VideoAppLifeCycleObserver extends WidgetsBindingObserver { + bool _wasPlayingBeforePause = false; + final VideoPlayerController _controller; + + _VideoAppLifeCycleObserver(this._controller); + + void initialize() { + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.paused: + _wasPlayingBeforePause = _controller.value.isPlaying; + _controller.pause(); + break; + case AppLifecycleState.resumed: + if (_wasPlayingBeforePause) { + _controller.play(); + } + break; + default: + } + } + + void dispose() { + WidgetsBinding.instance.removeObserver(this); + } +} + +/// Displays the video controlled by [controller]. +class VideoPlayer extends StatelessWidget { + final VideoPlayerController controller; + + VideoPlayer(this.controller); + + @override + Widget build(BuildContext context) { + return controller._textureId == null + ? new Container() + : new Texture(textureId: controller._textureId); + } +} + +class VideoProgressColors { + final Paint playedPaint; + final Paint bufferedPaint; + final Paint handlePaint; + final Paint disabledPaint; + + VideoProgressColors({ + Color playedColor: const Color.fromRGBO(255, 0, 0, 0.7), + Color bufferedColor: const Color.fromRGBO(30, 30, 200, 0.2), + Color handleColor: const Color.fromRGBO(200, 200, 200, 1.0), + Color disabledColor: const Color.fromRGBO(200, 200, 200, 0.5), + }) + : playedPaint = new Paint()..color = playedColor, + bufferedPaint = new Paint()..color = bufferedColor, + handlePaint = new Paint()..color = handleColor, + disabledPaint = new Paint()..color = disabledColor; +} + +/// Displays the play/buffering status of the video controlled by [controller]. +class VideoProgressBar extends StatefulWidget { + final VideoPlayerController controller; + final VideoProgressColors colors; + + VideoProgressBar(this.controller, {VideoProgressColors colors}) + : colors = colors ?? new VideoProgressColors(); + + @override + _VideoProgressBarState createState() { + return new _VideoProgressBarState(); + } +} + +class _VideoProgressBarState extends State { + VoidCallback listener; + + bool _controllerWasPlaying = false; + + _VideoProgressBarState() { + listener = () { + setState(() {}); + }; + } + + VideoPlayerController get controller => widget.controller; + + @override + void initState() { + super.initState(); + controller.addListener(listener); + } + + @override + void deactivate() { + controller.removeListener(listener); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + void seekToRelativePosition(Offset globalPosition) { + final RenderBox box = context.findRenderObject(); + final Offset tapPos = box.globalToLocal(globalPosition); + final double relative = tapPos.dx / box.size.width; + final Duration position = controller.value.duration * relative; + controller.seekTo(position); + } + + return new GestureDetector( + child: (controller.value.isErroneous) + ? new Text(controller.value.errorDescription) + : new CustomPaint( + painter: new ProgressBarPainter(controller.value, widget.colors), + ), + onHorizontalDragStart: (DragStartDetails details) { + if (!controller.value.initialized) { + return; + } + _controllerWasPlaying = controller.value.isPlaying; + if (_controllerWasPlaying) { + controller.pause(); + } + }, + onHorizontalDragUpdate: (DragUpdateDetails details) { + if (!controller.value.initialized) { + return; + } + seekToRelativePosition(details.globalPosition); + }, + onHorizontalDragEnd: (DragEndDetails details) { + if (_controllerWasPlaying) { + controller.play(); + } + }, + onTapDown: (TapDownDetails details) { + if (!controller.value.initialized) { + return; + } + seekToRelativePosition(details.globalPosition); + }, + ); + } +} + +class ProgressBarPainter extends CustomPainter { + VideoPlayerValue value; + VideoProgressColors colors; + ProgressBarPainter(this.value, this.colors); + + @override + bool shouldRepaint(CustomPainter painter) { + return true; + } + + @override + void paint(Canvas canvas, Size size) { + canvas.drawRect( + new Rect.fromPoints( + const Offset(0.0, 0.0), + new Offset(size.width, size.height), + ), + colors.disabledPaint, + ); + if (!value.initialized) { + return; + } + final double playedPart = value.position.inMilliseconds / + value.duration.inMilliseconds * + size.width; + for (DurationRange range in value.buffered) { + final double start = range.startFraction(value.duration) * size.width; + final double end = range.endFraction(value.duration) * size.width; + canvas.drawRect( + new Rect.fromPoints( + new Offset(start, 0.0), + new Offset(end, size.height), + ), + colors.bufferedPaint, + ); + } + canvas.drawRect( + new Rect.fromPoints(Offset.zero, new Offset(playedPart, size.height)), + colors.playedPaint, + ); + canvas.drawCircle( + new Offset(playedPart, size.height / 2), + size.height / 2, + colors.handlePaint, + ); + } +} diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml new file mode 100644 index 000000000000..023dd3b8dbf0 --- /dev/null +++ b/packages/video_player/pubspec.yaml @@ -0,0 +1,18 @@ +name: video_player +description: Flutter plugin for displaying video. +author: Flutter Team +version: 0.0.3 +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player + +flutter: + plugin: + androidPackage: io.flutter.videoplayer + pluginClass: VideoPlayerPlugin + +dependencies: + meta: "^1.0.5" + flutter: + sdk: flutter + +environment: + sdk: ">=1.8.0 <2.0.0" diff --git a/packages/video_player/video_player.iml b/packages/video_player/video_player.iml new file mode 100644 index 000000000000..033806516ca3 --- /dev/null +++ b/packages/video_player/video_player.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/video_player/video_player_android.iml b/packages/video_player/video_player_android.iml new file mode 100644 index 000000000000..462b903e05b6 --- /dev/null +++ b/packages/video_player/video_player_android.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + +