mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Gallery video demo (#13195)
This commit is contained in:
parent
1bd8ffbc46
commit
3a393249dc
@ -12,3 +12,4 @@ export 'material/material.dart';
|
||||
export 'pesto_demo.dart';
|
||||
export 'shrine_demo.dart';
|
||||
export 'typography_demo.dart';
|
||||
export 'video_demo.dart';
|
||||
|
376
examples/flutter_gallery/lib/demo/video_demo.dart
Normal file
376
examples/flutter_gallery/lib/demo/video_demo.dart
Normal file
@ -0,0 +1,376 @@
|
||||
// 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:connectivity/connectivity.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
// TODO(sigurdm): These should not be stored here.
|
||||
const String butterflyUri =
|
||||
'https://flutter.github.io/assets-for-api-docs/videos/butterfly.mp4';
|
||||
|
||||
const String beeUri =
|
||||
'https://flutter.github.io/assets-for-api-docs/videos/bee.mp4';
|
||||
|
||||
class VideoCard extends StatelessWidget {
|
||||
final VideoPlayerController controller;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
const VideoCard({Key key, this.controller, this.title, this.subtitle})
|
||||
: super(key: key);
|
||||
|
||||
Widget _buildInlineVideo() {
|
||||
return new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
|
||||
child: new Center(
|
||||
child: new AspectRatio(
|
||||
aspectRatio: 3 / 2,
|
||||
child: new Hero(
|
||||
tag: controller,
|
||||
child: new VideoPlayer(controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFullScreenVideo() {
|
||||
return new Scaffold(
|
||||
appBar: new AppBar(
|
||||
title: new Text(title),
|
||||
),
|
||||
body: new Center(
|
||||
child: new AspectRatio(
|
||||
aspectRatio: 3 / 2,
|
||||
child: new Hero(
|
||||
tag: controller,
|
||||
child: new VideoPlayPause(controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget fullScreenRoutePageBuilder(BuildContext context,
|
||||
Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return new AnimatedBuilder(
|
||||
child: _buildFullScreenVideo(),
|
||||
animation: animation,
|
||||
builder: (BuildContext context, Widget child) {
|
||||
// TODO(sigurdm): It seems we get a animation.value of 1.0
|
||||
// at first when entering the route. Find out how to avoid
|
||||
// this.
|
||||
controller.setVolume(animation.value);
|
||||
return child;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void pushFullScreenWidget() {
|
||||
final TransitionRoute<Null> route = new PageRouteBuilder<Null>(
|
||||
settings: new RouteSettings(name: title, isInitialRoute: false),
|
||||
pageBuilder: fullScreenRoutePageBuilder,
|
||||
);
|
||||
|
||||
route.completed.then((Null _) {
|
||||
controller.setVolume(0.0);
|
||||
});
|
||||
Navigator.of(context).push(route);
|
||||
}
|
||||
|
||||
return new Card(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new ListTile(title: new Text(title), subtitle: new Text(subtitle)),
|
||||
new GestureDetector(
|
||||
onTap: pushFullScreenWidget,
|
||||
child: _buildInlineVideo(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayPause extends StatefulWidget {
|
||||
final VideoPlayerController controller;
|
||||
|
||||
const VideoPlayPause(this.controller);
|
||||
|
||||
@override
|
||||
State createState() => new _VideoPlayPauseState();
|
||||
}
|
||||
|
||||
class _VideoPlayPauseState extends State<VideoPlayPause> {
|
||||
FadeAnimation imageFadeAnimation;
|
||||
VoidCallback listener;
|
||||
|
||||
_VideoPlayPauseState() {
|
||||
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) {
|
||||
final List<Widget> children = <Widget>[
|
||||
new GestureDetector(
|
||||
child: new VideoPlayer(controller),
|
||||
onTap: () {
|
||||
if (!controller.value.initialized) {
|
||||
return;
|
||||
}
|
||||
if (controller.value.isPlaying) {
|
||||
imageFadeAnimation = new FadeAnimation(
|
||||
child: new Icon(Icons.pause, size: 100.0),
|
||||
);
|
||||
controller.pause();
|
||||
} else {
|
||||
imageFadeAnimation = new FadeAnimation(
|
||||
child: new Icon(Icons.play_arrow, size: 100.0),
|
||||
);
|
||||
controller.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
new Center(child: imageFadeAnimation),
|
||||
];
|
||||
|
||||
if (!controller.value.initialized) {
|
||||
children.add(new Container());
|
||||
}
|
||||
|
||||
return new Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
fit: StackFit.passthrough,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FadeAnimation extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
|
||||
const FadeAnimation({
|
||||
this.child,
|
||||
this.duration: const Duration(milliseconds: 500),
|
||||
});
|
||||
|
||||
@override
|
||||
_FadeAnimationState createState() => new _FadeAnimationState();
|
||||
}
|
||||
|
||||
class _FadeAnimationState extends State<FadeAnimation>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectivityOverlay extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Completer<Null> connectedCompleter;
|
||||
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||
|
||||
const ConnectivityOverlay({
|
||||
this.child,
|
||||
this.connectedCompleter,
|
||||
this.scaffoldKey,
|
||||
});
|
||||
|
||||
@override
|
||||
_ConnectivityOverlayState createState() => new _ConnectivityOverlayState();
|
||||
}
|
||||
|
||||
class _ConnectivityOverlayState extends State<ConnectivityOverlay> {
|
||||
StreamSubscription<ConnectivityResult> connectivitySubscription;
|
||||
bool connected = true;
|
||||
|
||||
static const Widget errorSnackBar = const SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: const ListTile(
|
||||
title: const Text('No network'),
|
||||
subtitle: const Text(
|
||||
'To load the videos you must have an active network connection',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Stream<ConnectivityResult> connectivityStream() async* {
|
||||
final Connectivity connectivity = new Connectivity();
|
||||
ConnectivityResult previousResult = await connectivity.checkConnectivity();
|
||||
yield previousResult;
|
||||
await for (ConnectivityResult result
|
||||
in connectivity.onConnectivityChanged) {
|
||||
if (result != previousResult) {
|
||||
yield result;
|
||||
previousResult = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
connectivitySubscription = connectivityStream().listen(
|
||||
(ConnectivityResult connectivityResult) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
widget.scaffoldKey.currentState.showSnackBar(errorSnackBar);
|
||||
} else {
|
||||
if (!widget.connectedCompleter.isCompleted) {
|
||||
widget.connectedCompleter.complete(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
connectivitySubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
|
||||
class VideoDemo extends StatefulWidget {
|
||||
const VideoDemo({Key key}) : super(key: key);
|
||||
|
||||
static const String routeName = '/video';
|
||||
|
||||
@override
|
||||
_VideoDemoState createState() => new _VideoDemoState();
|
||||
}
|
||||
|
||||
class _VideoDemoState extends State<VideoDemo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final VideoPlayerController butterflyController = new VideoPlayerController(
|
||||
butterflyUri,
|
||||
);
|
||||
final VideoPlayerController beeController = new VideoPlayerController(
|
||||
beeUri,
|
||||
);
|
||||
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
final Completer<Null> connectedCompleter = new Completer<Null>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
Future<Null> initController(VideoPlayerController controller) async {
|
||||
controller.setLooping(true);
|
||||
controller.setVolume(0.0);
|
||||
controller.play();
|
||||
await connectedCompleter.future;
|
||||
await controller.initialize();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
initController(butterflyController);
|
||||
initController(beeController);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
butterflyController.dispose();
|
||||
beeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
key: scaffoldKey,
|
||||
appBar: new AppBar(
|
||||
title: const Text('Videos'),
|
||||
),
|
||||
body: new ConnectivityOverlay(
|
||||
child: new ListView(
|
||||
children: <Widget>[
|
||||
new VideoCard(
|
||||
title: 'Butterfly',
|
||||
subtitle: '… flutters by',
|
||||
controller: butterflyController,
|
||||
),
|
||||
new VideoCard(
|
||||
title: 'Bee',
|
||||
subtitle: '… gently buzzing',
|
||||
controller: beeController,
|
||||
),
|
||||
],
|
||||
),
|
||||
connectedCompleter: connectedCompleter,
|
||||
scaffoldKey: scaffoldKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -73,6 +73,13 @@ List<GalleryItem> _buildGalleryItems() {
|
||||
routeName: AnimationDemo.routeName,
|
||||
buildRoute: (BuildContext context) => const AnimationDemo(),
|
||||
),
|
||||
new GalleryItem(
|
||||
title: 'Video',
|
||||
subtitle: 'Video playback',
|
||||
category: 'Demos',
|
||||
routeName: VideoDemo.routeName,
|
||||
buildRoute: (BuildContext context) => const VideoDemo(),
|
||||
),
|
||||
// Material Components
|
||||
new GalleryItem(
|
||||
title: 'Bottom navigation',
|
||||
|
@ -4,9 +4,11 @@ dependencies:
|
||||
sdk: flutter
|
||||
collection: 1.14.3
|
||||
intl: 0.15.2
|
||||
connectivity: 0.1.0
|
||||
string_scanner: 1.0.2
|
||||
url_launcher: 0.4.2+5
|
||||
cupertino_icons: 0.1.1
|
||||
video_player: 0.0.5
|
||||
|
||||
# Also update dev/benchmarks/complex_layout/pubspec.yaml
|
||||
flutter_gallery_assets:
|
||||
|
Loading…
Reference in New Issue
Block a user