mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

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.
349 lines
8.3 KiB
Dart
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(),
|
|
);
|
|
}
|
|
}
|