diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 97949a26aa..18df322a63 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,3 +15,5 @@ dev_dependencies: css_colors: 1.0.2 web_socket_channel: 1.0.9 sqflite: ^1.1.0 + camera: ^0.4.0+3 + video_player: ^0.10.0+2 diff --git a/src/docs/cookbook/persistence/key-value.md b/src/docs/cookbook/persistence/key-value.md index a3e9ecb2c1..20a1195789 100644 --- a/src/docs/cookbook/persistence/key-value.md +++ b/src/docs/cookbook/persistence/key-value.md @@ -4,8 +4,8 @@ prev: title: Reading and Writing Files path: /docs/cookbook/persistence/reading-writing-files next: - title: An introduction to integration testing - path: /docs/cookbook/testing/integration/introduction + title: Play and pause a video + path: /docs/cookbook/plugins/play-video --- If you have a relatively small collection of key-values that you'd like diff --git a/src/docs/cookbook/plugins/index.md b/src/docs/cookbook/plugins/index.md new file mode 100644 index 0000000000..8ec66e28d2 --- /dev/null +++ b/src/docs/cookbook/plugins/index.md @@ -0,0 +1,5 @@ +--- +title: Plugins +--- + +{% include cookbook_group_index.md %} diff --git a/src/docs/cookbook/plugins/picture-using-camera.md b/src/docs/cookbook/plugins/picture-using-camera.md new file mode 100644 index 0000000000..965465567e --- /dev/null +++ b/src/docs/cookbook/plugins/picture-using-camera.md @@ -0,0 +1,368 @@ +--- +title: Take a picture using the Camera +prev: + title: Play and pause a video + path: /docs/cookbook/plugins/play-video +next: + title: An introduction to integration testing + path: /docs/cookbook/testing/integration/introduction +--- + +Many apps require working with the device's cameras to take photos and videos. +Flutter provides the [`camera`](https://pub.dartlang.org/packages/camera) plugin +for this purpose. The `camera` plugin provides tools to get a list of the +available cameras, display a preview coming from a specific camera, and take +photos or videos. + +This recipe demonstrates how to use the `camera` plugin to display a preview, +take a photo, and display it. + +## Directions + + 1. Add the required dependencies + 2. Get a list of the available cameras + 3. Create and initialize the `CameraController` + 4. Use a `CameraPreview` to display the camera's feed + 5. Take a picture with the `CameraController` + 6. Display the picture with an `Image` Widget + +## 1. Add the required dependencies + +To complete this recipe, you need to add three dependencies to your app: + + - [`camera`](https://pub.dartlang.org/packages/camera) - Provides tools to work with the cameras on device + - [`path_provider`](https://pub.dartlang.org/packages/path_provider) - Finds the correct paths to store images + - [`path`](https://pub.dartlang.org/packages/path) - Creates paths that work on any platform + +```yaml +dependencies: + flutter: + sdk: flutter + camera: + path_provider: + path: +``` + +## 2. Get a list of the available cameras + +Next, you can get a list of available cameras using the `camera` plugin. + + +```dart +// Obtain a list of the available cameras on the device. +final cameras = await availableCameras(); + +// Get a specific camera from the list of available cameras +final firstCamera = cameras.first; +``` + +## 3. Create and initialize the `CameraController` + +Once you have a camera to work with, you need to create and initialize a +`CameraController`. This process establishes a connection to the device's camera +that allows you to control the camera and display a preview of the camera's +feed. + +To achieve this, please: + + 1. Create a `StatefulWidget` with a companion `State` class + 2. Add a variable to the `State` class to store the `CameraController` + 3. Add a variable to the `State` class to store the `Future` returned from + `CameraController.initialize` + 4. Create and initialize the controller in the `initState` method + 5. Dispose of the controller in the `dispose` method + + +```dart +// A screen that takes in a list of Cameras and the Directory to store images. +class TakePictureScreen extends StatefulWidget { + final CameraDescription camera; + + const TakePictureScreen({ + Key key, + @required this.camera, + }) : super(key: key); + + @override + TakePictureScreenState createState() => TakePictureScreenState(); +} + +class TakePictureScreenState extends State { + // Add two variables to the state class to store the CameraController and + // the Future + CameraController _controller; + Future _initializeControllerFuture; + + @override + void initState() { + super.initState(); + // In order to display the current output from the Camera, you need to + // create a CameraController. + _controller = CameraController( + // Get a specific camera from the list of available cameras + widget.camera, + // Define the resolution to use + ResolutionPreset.medium, + ); + + // Next, you need to initialize the controller. This will return a Future + _initializeControllerFuture = _controller.initialize(); + } + + @override + void dispose() { + // Make sure to dispose of the controller when the Widget is disposed + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Fill this out in the next steps + } +} +``` + +{{site.alert.warning}} +If you do not initialize the `CameraController`, you will *not* be able to work with +the camera. +{{site.alert.end}} + +## 4. Use a `CameraPreview` to display the camera's feed + +Next, you can use the `CameraPreview` Widget from the `camera` package to +display a preview of the camera's feed. + +Remember: You must wait until the controller has finished initializing before +working with the camera. Therefore, you must wait for the +`_initializeControllerFuture` created in the previous step to complete before +showing a `CameraPreview`. + +You can use a +[`FutureBuilder`](https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html) +for exactly this purpose. + + +```dart +// You must wait until the controller is initialized before displaying the +// camera preview. Use a FutureBuilder to display a loading spinner until the +// controller has finished initializing +FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview + return CameraPreview(_controller); + } else { + // Otherwise, display a loading indicator + return Center(child: CircularProgressIndicator()); + } + }, +) +``` + +## 5. Take a picture with the `CameraController` + +You can also use the `CameraController` to take pictures using the +[`takePicture`](https://pub.dartlang.org/documentation/camera/latest/camera/CameraController/takePicture.html) +method. In this example, create a `FloatingActionButton` that takes a picture +using the `CameraController` when a user taps on the button. + +Saving a picture requires 3 steps: + + 1. Ensure the camera is initialized + 2. Construct a path that defines where the picture should be saved + 3. Use the controller to take a picture and save the result to the path + +It is good practice to wrap these operations in a `try / catch` block in order +to handle any errors that might occur. + + +```dart +FloatingActionButton( + child: Icon(Icons.camera_alt), + // Provide an onPressed callback + onPressed: () async { + // Take the Picture in a try / catch block. If anything goes wrong, + // catch the error. + try { + // Ensure the camera is initialized + await _initializeControllerFuture; + + // Construct the path where the image should be saved using the path + // package. + final path = join( + // In this example, store the picture in the temp directory. Find + // the temp directory using the `path_provider` plugin. + (await getTemporaryDirectory()).path, + '${DateTime.now()}.png', + ); + + // Attempt to take a picture and log where it's been saved + await _controller.takePicture(path); + } catch (e) { + // If an error occurs, log the error to the console. + print(e); + } + }, +) +``` +## 6. Display the picture with an `Image` Widget + +If you take the picture successfully, you can then display the saved picture +using an `Image` widget. In this case, the picture will be stored as a file on +the device. + +Therefore, you must provide a `File` to the `Image.file` constructor. You +can create an instance of the `File` class by passing in the path you created in +the previous step. + + +```dart +Image.file(File('path/to/my/picture.png')) +``` + +## Complete Example + +```dart +import 'dart:async'; +import 'dart:io'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:path/path.dart' show join; +import 'package:path_provider/path_provider.dart'; + +Future main() async { + // Obtain a list of the available cameras on the device. + final cameras = await availableCameras(); + + // Get a specific camera from the list of available cameras + final firstCamera = cameras.first; + + runApp( + MaterialApp( + theme: ThemeData.dark(), + home: TakePictureScreen( + // Pass the appropriate camera to the TakePictureScreen Widget + camera: firstCamera, + ), + ), + ); +} + +// A screen that allows users to take a picture using a given camera +class TakePictureScreen extends StatefulWidget { + final CameraDescription camera; + + const TakePictureScreen({ + Key key, + @required this.camera, + }) : super(key: key); + + @override + TakePictureScreenState createState() => TakePictureScreenState(); +} + +class TakePictureScreenState extends State { + CameraController _controller; + Future _initializeControllerFuture; + + @override + void initState() { + super.initState(); + // In order to display the current output from the Camera, you need to + // create a CameraController. + _controller = CameraController( + // Get a specific camera from the list of available cameras + widget.camera, + // Define the resolution to use + ResolutionPreset.medium, + ); + + // Next, you need to initialize the controller. This will return a Future! + _initializeControllerFuture = _controller.initialize(); + } + + @override + void dispose() { + // Make sure to dispose of the controller when the Widget is disposed + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Take a picture!')), + // You must wait until the controller is initialized before displaying the + // camera preview. Use a FutureBuilder to display a loading spinner until + // the controller has finished initializing! + body: FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview + return CameraPreview(_controller); + } else { + // Otherwise, display a loading indicator + return Center(child: CircularProgressIndicator()); + } + }, + ), + floatingActionButton: FloatingActionButton( + child: Icon(Icons.camera_alt), + // Provide an onPressed callback + onPressed: () async { + // Take the Picture in a try / catch block. If anything goes wrong, + // catch the error. + try { + // Ensure the camera is initialized + await _initializeControllerFuture; + + // Construct the path where the image should be saved using the path + // package. + final path = join( + // In this example, store the picture in the temp directory. Find + // the temp directory using the `path_provider` plugin. + (await getTemporaryDirectory()).path, + '${DateTime.now()}.png', + ); + + // Attempt to take a picture and log where it's been saved + await _controller.takePicture(path); + + // If the picture was taken, display it on a new screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DisplayPictureScreen(imagePath: path), + ), + ); + } catch (e) { + // If an error occurs, log the error to the console. + print(e); + } + }, + ), + ); + } +} + +// A Widget that displays the picture taken by the user +class DisplayPictureScreen extends StatelessWidget { + final String imagePath; + + const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Display the Picture')), + // The image is stored as a file on the device. Use the `Image.file` + // constructor with the given path to display the image + body: Image.file(File(imagePath)), + ); + } +} +``` diff --git a/src/docs/cookbook/plugins/play-video.md b/src/docs/cookbook/plugins/play-video.md new file mode 100644 index 0000000000..839b43f431 --- /dev/null +++ b/src/docs/cookbook/plugins/play-video.md @@ -0,0 +1,330 @@ +--- +title: Play and pause a video +prev: + title: Storing key-value data on disk + path: /docs/cookbook/persistence/key-value +next: + title: Take a picture using the Camera + path: /docs/cookbook/plugins/picture-using-camera +--- + +Playing videos is a common task in app development, and Flutter apps are no +exception. In order to play videos, the Flutter team provides the +[`video_player`](https://pub.dartlang.org/packages/video_player) plugin. You can +use the `video_player` plugin to play videos stored on the file system, as an +asset, or from the internet. + +On iOS, the `video_player` plugin makes use of +[`AVPlayer`](https://developer.apple.com/documentation/avfoundation/avplayer) to +handle playback. On Android, it uses +[`ExoPlayer`](https://google.github.io/ExoPlayer/). + +This recipe demonstrates how to use the `video_player` package to stream a +video from the internet with basic play and pause controls. + +## Directions + + 1. Add the `video_player` dependency + 2. Add permissions to your app + 3. Create and initialize a `VideoPlayerController` + 4. Display the video player + 5. Play and pause the video + +## 1. Add the `video_player` dependency + +This recipe depends on one Flutter plugin: `video_player`. First, add this +dependency to your `pubspec.yaml`. + +```yaml +dependencies: + flutter: + sdk: flutter + video_player: +``` + +## 2. Add permissions to your app + +Next, you need to ensure your app has the correct permissions to stream videos +from the internet. To do so, update your `android` and `ios` configurations. + +### Android + +Add the following permission to the `AndroidManifest.xml` just after the +`` definition. The `AndroidManifest.xml` can be found at `/android/app/src/main/AndroidManifest.xml` + + +```xml + + + + + + + +``` + +### iOS + +For iOS, you need to add the following to your `Info.plist` file found at +`/ios/Runner/Info.plist`. + + +```xml +NSAppTransportSecurity + + NSAllowsArbitraryLoads + + +``` + +{{site.alert.warning}} +The `video_player` plugin does not work on iOS simulators. You must test videos +on real iOS devices. +{{site.alert.end}} + +## 3. Create and initialize a `VideoPlayerController` + +Now that you have the `video_player` plugin installed with the correct +permissions, you need to create a `VideoPlayerController`. The +`VideoPlayerController` class allows you to connect to different types of +videos and control playback. + +Before you can play videos, you must also `initialize` the controller. This will +establish the connection to the video and prepare the controller for playback. + +To create an initialize the `VideoPlayerController`, please: + + 1. Create a `StatefulWidget` with a companion `State` class + 2. Add a variable to the `State` class to store the `VideoPlayerController` + 3. Add a variable to the `State` class to store the `Future` returned from + `VideoPlayerController.initialize` + 4. Create and initialize the controller in the `initState` method + 5. Dispose of the controller in the `dispose` method + + +```dart +class VideoPlayerScreen extends StatefulWidget { + VideoPlayerScreen({Key key}) : super(key: key); + + @override + _VideoPlayerScreenState createState() => _VideoPlayerScreenState(); +} + +class _VideoPlayerScreenState extends State { + VideoPlayerController _controller; + Future _initializeVideoPlayerFuture; + + @override + void initState() { + // Create an store the VideoPlayerController. The VideoPlayerController + // offers several different constructors to play videos from assets, files, + // or the internet. + _controller = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', + ); + + _initializeVideoPlayerFuture = _controller.initialize(); + + super.initState(); + } + + @override + void dispose() { + // Ensure you dispose the VideoPlayerController to free up resources + _controller.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Show the video in the next step + } +} +``` + +## 4. Display the video player + +Now, it's time to display the video. The `video_player` plugin provides the +[`VideoPlayer`](https://pub.dartlang.org/documentation/video_player/latest/video_player/VideoPlayer-class.html) +Widget to display the video initialized by the `VideoPlayerController`. By +default, the `VideoPlayer` Widget will take up as much space as possible. This +often isn't ideal for videos because they are meant to be displayed in a +specific aspect ratio, such as 16x9 or 4x3. + +Therefore, you can wrap the `VideoPlayer` widget in an +[`AspectRatio`](https://docs.flutter.io/flutter/widgets/AspectRatio-class.html) +widget to ensure the video is the correct proportions. + +Furthermore, you must display the `VideoPlayer` widget after the +`_initializeVideoPlayerFuture` completes. You can use a `FutureBuilder` to +display a loading spinner until finishes initializing. Note: initializing the +controller does not begin playback. + + +```dart +// Use a FutureBuilder to display a loading spinner while you wait for the +// VideoPlayerController to finish initializing. +FutureBuilder( + future: _initializeVideoPlayerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the VideoPlayerController has finished initialization, use + // the data it provides to limit the Aspect Ratio of the VideoPlayer + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + // Use the VideoPlayer widget to display the video + child: VideoPlayer(_controller), + ); + } else { + // If the VideoPlayerController is still initializing, show a + // loading spinner + return Center(child: CircularProgressIndicator()); + } + }, +) +``` + +## 5. Play and pause the video + +By default, the video will be displayed in a paused state. To start playback, +call the +[`play`](https://pub.dartlang.org/documentation/video_player/latest/video_player/VideoPlayerController/play.html) +method provided by the `VideoPlayerController`. To pause playback, call the +[`pause`](https://pub.dartlang.org/documentation/video_player/latest/video_player/VideoPlayerController/pause.html) +method. + +For this example, add a `FloatingActionButton` to your app that displays a play +or pause icon depending on the situation. When the user taps the button, play +the video if it's currently paused, or pause the video if it's playing. + + +```dart +FloatingActionButton( + onPressed: () { + // Wrap the play or pause in a call to `setState`. This will ensure + // the correct icon is shown + setState(() { + // If the video is playing, pause it. + if (_controller.value.isPlaying) { + _controller.pause(); + } else { + // If the video is paused, play it + _controller.play(); + } + }); + }, + // Display the correct icon depending on the state of the player. + child: Icon( + _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, + ), +) +``` + +## Complete Example + +```dart +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +void main() => runApp(VideoPlayerApp()); + +class VideoPlayerApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Video Player Demo', + home: VideoPlayerScreen(), + ); + } +} + +class VideoPlayerScreen extends StatefulWidget { + VideoPlayerScreen({Key key}) : super(key: key); + + @override + _VideoPlayerScreenState createState() => _VideoPlayerScreenState(); +} + +class _VideoPlayerScreenState extends State { + VideoPlayerController _controller; + Future _initializeVideoPlayerFuture; + + @override + void initState() { + // Create and store the VideoPlayerController. The VideoPlayerController + // offers several different constructors to play videos from assets, files, + // or the internet. + _controller = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', + ); + + // Initialize the controller and store the Future for later use + _initializeVideoPlayerFuture = _controller.initialize(); + + // Use the controller to loop the video + _controller.setLooping(true); + + super.initState(); + } + + @override + void dispose() { + // Ensure you dispose the VideoPlayerController to free up resources + _controller.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Butterfly Video'), + ), + // Use a FutureBuilder to display a loading spinner while you wait for the + // VideoPlayerController to finish initializing. + body: FutureBuilder( + future: _initializeVideoPlayerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the VideoPlayerController has finished initialization, use + // the data it provides to limit the Aspect Ratio of the Video + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + // Use the VideoPlayer widget to display the video + child: VideoPlayer(_controller), + ); + } else { + // If the VideoPlayerController is still initializing, show a + // loading spinner + return Center(child: CircularProgressIndicator()); + } + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Wrap the play or pause in a call to `setState`. This will ensure + // the correct icon is shown! + setState(() { + // If the video is playing, pause it. + if (_controller.value.isPlaying) { + _controller.pause(); + } else { + // If the video is paused, play it! + _controller.play(); + } + }); + }, + // Display the correct icon depending on the state of the player. + child: Icon( + _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, + ), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} +``` diff --git a/src/docs/cookbook/testing/integration/introduction.md b/src/docs/cookbook/testing/integration/introduction.md index dfc8af8ad4..686a2ea499 100644 --- a/src/docs/cookbook/testing/integration/introduction.md +++ b/src/docs/cookbook/testing/integration/introduction.md @@ -2,8 +2,8 @@ title: An introduction to integration testing short-title: Introduction prev: - title: Storing key-value data on disk - path: /docs/cookbook/persistence/key-value + title: Take a picture using the Camera + path: /docs/cookbook/plugins/picture-using-camera next: title: Performance profiling path: /docs/cookbook/testing/integration/profiling