mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 23:39:14 +00:00
Update web/ samples to work with Flutter SDK (#134)
* add package:http dependency in dad_jokes * add package:http dependency in filipino_cuisine * don't build package:http demos until flutter/flutter#34858 is resolved * update gallery * update github_dataviz * update particle_background * don't build github_dataviz (uses package:http) * update slide_puzzle * update spinning_square * update timeflow * update vision_challenge * update charts * update dad_jokes * update filipino cuisine * ignore build output * update timeflow and vision_challenge * update slide_puzzle * don't commit build/ directory * move preview.png images to assets * fix path url join * update readme * update web/readme.md
This commit is contained in:
434
web/gallery/lib/demo/video_demo.dart
Normal file
434
web/gallery/lib/demo/video_demo.dart
Normal file
@@ -0,0 +1,434 @@
|
||||
// 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 'dart:io';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:device_info/device_info.dart';
|
||||
|
||||
class VideoCard extends StatelessWidget {
|
||||
const VideoCard({ Key key, this.controller, this.title, this.subtitle }) : super(key: key);
|
||||
|
||||
final VideoPlayerController controller;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
Widget _buildInlineVideo() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 3 / 2,
|
||||
child: Hero(
|
||||
tag: controller,
|
||||
child: VideoPlayerLoading(controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFullScreenVideo() {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
body: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 3 / 2,
|
||||
child: Hero(
|
||||
tag: controller,
|
||||
child: VideoPlayPause(controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget fullScreenRoutePageBuilder(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return _buildFullScreenVideo();
|
||||
}
|
||||
|
||||
void pushFullScreenWidget() {
|
||||
final TransitionRoute<void> route = PageRouteBuilder<void>(
|
||||
settings: RouteSettings(name: title, isInitialRoute: false),
|
||||
pageBuilder: fullScreenRoutePageBuilder,
|
||||
);
|
||||
|
||||
route.completed.then((void value) {
|
||||
controller.setVolume(0.0);
|
||||
});
|
||||
|
||||
controller.setVolume(1.0);
|
||||
Navigator.of(context).push(route);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Card(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ListTile(title: Text(title), subtitle: Text(subtitle)),
|
||||
GestureDetector(
|
||||
onTap: pushFullScreenWidget,
|
||||
child: _buildInlineVideo(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayerLoading extends StatefulWidget {
|
||||
const VideoPlayerLoading(this.controller);
|
||||
|
||||
final VideoPlayerController controller;
|
||||
|
||||
@override
|
||||
_VideoPlayerLoadingState createState() => _VideoPlayerLoadingState();
|
||||
}
|
||||
|
||||
class _VideoPlayerLoadingState extends State<VideoPlayerLoading> {
|
||||
bool _initialized;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initialized = widget.controller.value.initialized;
|
||||
widget.controller.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final bool controllerInitialized = widget.controller.value.initialized;
|
||||
if (_initialized != controllerInitialized) {
|
||||
setState(() {
|
||||
_initialized = controllerInitialized;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_initialized) {
|
||||
return VideoPlayer(widget.controller);
|
||||
}
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
VideoPlayer(widget.controller),
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
fit: StackFit.expand,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayPause extends StatefulWidget {
|
||||
const VideoPlayPause(this.controller);
|
||||
|
||||
final VideoPlayerController controller;
|
||||
|
||||
@override
|
||||
State createState() => _VideoPlayPauseState();
|
||||
}
|
||||
|
||||
class _VideoPlayPauseState extends State<VideoPlayPause> {
|
||||
_VideoPlayPauseState() {
|
||||
listener = () {
|
||||
if (mounted)
|
||||
setState(() { });
|
||||
};
|
||||
}
|
||||
|
||||
FadeAnimation imageFadeAnimation;
|
||||
VoidCallback listener;
|
||||
|
||||
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) {
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
child: VideoPlayerLoading(controller),
|
||||
onTap: () {
|
||||
if (!controller.value.initialized) {
|
||||
return;
|
||||
}
|
||||
if (controller.value.isPlaying) {
|
||||
imageFadeAnimation = const FadeAnimation(
|
||||
child: Icon(Icons.pause, size: 100.0),
|
||||
);
|
||||
controller.pause();
|
||||
} else {
|
||||
imageFadeAnimation = const FadeAnimation(
|
||||
child: Icon(Icons.play_arrow, size: 100.0),
|
||||
);
|
||||
controller.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
Center(child: imageFadeAnimation),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FadeAnimation extends StatefulWidget {
|
||||
const FadeAnimation({
|
||||
this.child,
|
||||
this.duration = const Duration(milliseconds: 500),
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
|
||||
@override
|
||||
_FadeAnimationState createState() => _FadeAnimationState();
|
||||
}
|
||||
|
||||
class _FadeAnimationState extends State<FadeAnimation> with SingleTickerProviderStateMixin {
|
||||
AnimationController animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
animationController = 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
|
||||
? Opacity(
|
||||
opacity: 1.0 - animationController.value,
|
||||
child: widget.child,
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectivityOverlay extends StatefulWidget {
|
||||
const ConnectivityOverlay({
|
||||
this.child,
|
||||
this.connectedCompleter,
|
||||
this.scaffoldKey,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Completer<void> connectedCompleter;
|
||||
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||
|
||||
@override
|
||||
_ConnectivityOverlayState createState() => _ConnectivityOverlayState();
|
||||
}
|
||||
|
||||
class _ConnectivityOverlayState extends State<ConnectivityOverlay> {
|
||||
StreamSubscription<ConnectivityResult> connectivitySubscription;
|
||||
bool connected = true;
|
||||
|
||||
static const Widget errorSnackBar = SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: ListTile(
|
||||
title: Text('No network'),
|
||||
subtitle: Text(
|
||||
'To load the videos you must have an active network connection',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Stream<ConnectivityResult> connectivityStream() async* {
|
||||
final Connectivity connectivity = 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() => _VideoDemoState();
|
||||
}
|
||||
|
||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
|
||||
Future<bool> isIOSSimulator() async {
|
||||
return Platform.isIOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice;
|
||||
}
|
||||
|
||||
class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMixin {
|
||||
final VideoPlayerController butterflyController = VideoPlayerController.asset(
|
||||
'videos/butterfly.mp4',
|
||||
package: 'flutter_gallery_assets',
|
||||
);
|
||||
|
||||
// TODO(sigurdm): This should not be stored here.
|
||||
static const String beeUri = 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
||||
final VideoPlayerController beeController = VideoPlayerController.network(beeUri);
|
||||
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final Completer<void> connectedCompleter = Completer<void>();
|
||||
bool isSupported = true;
|
||||
bool isDisposed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
Future<void> initController(VideoPlayerController controller, String name) async {
|
||||
print('> VideoDemo initController "$name" ${isDisposed ? "DISPOSED" : ""}');
|
||||
controller.setLooping(true);
|
||||
controller.setVolume(0.0);
|
||||
controller.play();
|
||||
await connectedCompleter.future;
|
||||
await controller.initialize();
|
||||
if (mounted) {
|
||||
print('< VideoDemo initController "$name" done ${isDisposed ? "DISPOSED" : ""}');
|
||||
setState(() { });
|
||||
}
|
||||
}
|
||||
|
||||
initController(butterflyController, 'butterfly');
|
||||
initController(beeController, 'bee');
|
||||
isIOSSimulator().then<void>((bool result) {
|
||||
isSupported = !result;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('> VideoDemo dispose');
|
||||
isDisposed = true;
|
||||
butterflyController.dispose();
|
||||
beeController.dispose();
|
||||
print('< VideoDemo dispose');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: const Text('Videos'),
|
||||
),
|
||||
body: isSupported
|
||||
? ConnectivityOverlay(
|
||||
child: Scrollbar(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
VideoCard(
|
||||
title: 'Butterfly',
|
||||
subtitle: '… flutters by',
|
||||
controller: butterflyController,
|
||||
),
|
||||
VideoCard(
|
||||
title: 'Bee',
|
||||
subtitle: '… gently buzzing',
|
||||
controller: beeController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
connectedCompleter: connectedCompleter,
|
||||
scaffoldKey: scaffoldKey,
|
||||
)
|
||||
: const Center(
|
||||
child: Text(
|
||||
'Video playback not supported on the iOS Simulator.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user