flutter/dev/integration_tests/flutter_gallery/lib/demo/video_demo.dart
Gray Mackall 4a84fb0fea
Remove discontinued device_info and connectivity plugins from flutter_gallery, roll pub packages (#150585)
Removes these two discontinued plugins from `dev/integration_tests/flutter_gallery`

[`device_info`](https://pub.dev/packages/device_info):
Apparently the video playback doesn't work on iOS simulators (I wasn't able to verify this, as I don't have an iOS simulator installed). I removed the guard against running on those simulators, and replaced with a note in the README.

[`connectivity`](https://pub.dev/packages/connectivity):
This plugin was used to play the bee video from the network. I changed the demo so that the bee video is instead also played from an asset (like its friend the butterfly), and then removed the use of the plugin.

Unblocks the re-land of https://github.com/flutter/engine/pull/53462 (itself a reland 🙂), because of https://github.com/flutter/flutter/pull/150465#issuecomment-2181403712.
2024-06-21 23:10:24 +00:00

349 lines
8.3 KiB
Dart

// Copyright 2014 The Flutter 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:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoCard extends StatelessWidget {
const VideoCard({ super.key, this.controller, this.title, this.subtitle });
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),
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, {super.key});
final VideoPlayerController? controller;
@override
State<VideoPlayerLoading> createState() => _VideoPlayerLoadingState();
}
class _VideoPlayerLoadingState extends State<VideoPlayerLoading> {
bool? _initialized;
@override
void initState() {
super.initState();
_initialized = widget.controller!.value.isInitialized;
widget.controller!.addListener(() {
if (!mounted) {
return;
}
final bool controllerInitialized = widget.controller!.value.isInitialized;
if (_initialized != controllerInitialized) {
setState(() {
_initialized = controllerInitialized;
});
}
});
}
@override
Widget build(BuildContext context) {
if (_initialized!) {
return VideoPlayer(widget.controller!);
}
return Stack(
fit: StackFit.expand,
children: <Widget>[
VideoPlayer(widget.controller!),
const Center(child: CircularProgressIndicator()),
],
);
}
}
class VideoPlayPause extends StatefulWidget {
const VideoPlayPause(this.controller, {super.key});
final VideoPlayerController? controller;
@override
State createState() => _VideoPlayPauseState();
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
if (mounted) {
setState(() { });
}
};
}
FadeAnimation? imageFadeAnimation;
late 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.isInitialized) {
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({
super.key,
this.child,
this.duration = const Duration(milliseconds: 500),
});
final Widget? child;
final Duration duration;
@override
State<FadeAnimation> createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation> with SingleTickerProviderStateMixin {
late 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 VideoDemo extends StatefulWidget {
const VideoDemo({ super.key });
static const String routeName = '/video';
@override
State<VideoDemo> createState() => _VideoDemoState();
}
class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMixin {
final VideoPlayerController butterflyController = VideoPlayerController.asset(
'videos/butterfly.mp4',
package: 'flutter_gallery_assets',
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
final VideoPlayerController beeController = VideoPlayerController.asset(
'videos/bee.mp4',
package: 'flutter_gallery_assets',
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
bool isDisposed = false;
// Only non-test mobile environments are supported for this demo.
bool isSupported = Platform.isAndroid || Platform.isIOS;
@override
void initState() {
super.initState();
if (!isSupported) {
return;
}
Future<void> initController(VideoPlayerController controller, String name) async {
controller.setLooping(true);
controller.setVolume(0.0);
controller.play();
await controller.initialize();
if (mounted) {
setState(() { });
}
}
initController(butterflyController, 'butterfly');
initController(beeController, 'bee');
}
@override
void dispose() {
isDisposed = true;
butterflyController.dispose();
beeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Videos'),
),
body: isSupported
? Scrollbar(
child: ListView(
primary: true,
children: <Widget>[
VideoCard(
title: 'Butterfly',
subtitle: '… flutters by',
controller: butterflyController,
),
VideoCard(
title: 'Bee',
subtitle: '… gently buzzing',
controller: beeController,
),
],
),
)
: const Placeholder(),
);
}
}