mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-03-11 17:47:47 -05:00
With this commit we are wrapping all action widgets in a `SafeArea` widget, to improve the alignment of the actions.
293 lines
9.4 KiB
Dart
293 lines
9.4 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:media_kit/media_kit.dart';
|
|
import 'package:media_kit_video/media_kit_video.dart';
|
|
|
|
import 'package:feeddeck/utils/constants.dart';
|
|
|
|
/// The [ItemVideos] widget is used to display a list of videos, which can be
|
|
/// played by the user. If the [videos] list is empty or null, the widget will
|
|
/// not be displayed.
|
|
class ItemVideos extends StatelessWidget {
|
|
const ItemVideos({
|
|
super.key,
|
|
required this.videos,
|
|
});
|
|
|
|
final List<String>? videos;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (videos == null || videos!.isEmpty) {
|
|
return Container();
|
|
}
|
|
|
|
return ListView.builder(
|
|
shrinkWrap: true,
|
|
itemCount: videos!.length,
|
|
itemBuilder: (context, index) {
|
|
return ItemVideoPlayer(video: videos![index]);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The [ItemVideoQuality] class is used to store the different qualities of a
|
|
/// video. It is used in combination with the [ItemVideoPlayer] widget.
|
|
class ItemVideoQuality {
|
|
const ItemVideoQuality({
|
|
required this.quality,
|
|
required this.video,
|
|
});
|
|
|
|
final String quality;
|
|
final String video;
|
|
}
|
|
|
|
/// The [ItemVideoPlayer] widget is used to display a video, which can be played
|
|
/// by the user. It should be used in combination with the [ItemVideos] widget
|
|
/// and is responsible for the actual implementation of the video player.
|
|
///
|
|
/// If the provided [video] doesn't contain the audio stream it can be passed
|
|
/// via the [audio] parameter.
|
|
///
|
|
/// The optional [qualities] parameter can be used to display a list of
|
|
/// different qualities for the video, so that a user can select a lower quality
|
|
/// if the video is not loading fast enough.
|
|
class ItemVideoPlayer extends StatefulWidget {
|
|
const ItemVideoPlayer({
|
|
super.key,
|
|
required this.video,
|
|
this.audio,
|
|
this.qualities,
|
|
});
|
|
|
|
final String video;
|
|
final String? audio;
|
|
final List<ItemVideoQuality>? qualities;
|
|
|
|
@override
|
|
State<ItemVideoPlayer> createState() => _ItemVideoPlayerState();
|
|
}
|
|
|
|
class _ItemVideoPlayerState extends State<ItemVideoPlayer> {
|
|
late final player = Player();
|
|
late final controller = VideoController(player);
|
|
|
|
/// [_buildQualityButton] returns a button which can be used to display a list
|
|
/// of different qualities for the video, so that a user can select a lower
|
|
/// quality if the video is not loading fast enough.
|
|
Widget _buildQualityButton() {
|
|
return IconButton(
|
|
onPressed: () {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
isDismissible: true,
|
|
useSafeArea: true,
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
constraints: const BoxConstraints(
|
|
maxWidth: Constants.centeredFormMaxWidth,
|
|
),
|
|
builder: (BuildContext context) {
|
|
return SafeArea(
|
|
child: Container(
|
|
margin: const EdgeInsets.all(
|
|
Constants.spacingMiddle,
|
|
),
|
|
padding: const EdgeInsets.only(
|
|
left: Constants.spacingMiddle,
|
|
right: Constants.spacingMiddle,
|
|
),
|
|
decoration: const BoxDecoration(
|
|
color: Constants.background,
|
|
borderRadius: BorderRadius.all(
|
|
Radius.circular(Constants.spacingMiddle),
|
|
),
|
|
),
|
|
child: Wrap(
|
|
alignment: WrapAlignment.center,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: widget.qualities!
|
|
.asMap()
|
|
.entries
|
|
.map((quality) {
|
|
if (quality.key == widget.qualities!.length - 1) {
|
|
return [
|
|
ListTile(
|
|
mouseCursor: SystemMouseCursors.click,
|
|
onTap: () async {
|
|
Navigator.of(context).pop();
|
|
await _playerOpen(quality.value.video);
|
|
},
|
|
title: Text(quality.value.quality),
|
|
),
|
|
];
|
|
}
|
|
|
|
return [
|
|
ListTile(
|
|
mouseCursor: SystemMouseCursors.click,
|
|
onTap: () async {
|
|
Navigator.of(context).pop();
|
|
await _playerOpen(quality.value.video);
|
|
},
|
|
title: Text(quality.value.quality),
|
|
),
|
|
const Divider(
|
|
color: Constants.dividerColor,
|
|
height: 1,
|
|
thickness: 1,
|
|
),
|
|
];
|
|
})
|
|
.expand((e) => e)
|
|
.toList(),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
icon: const Icon(Icons.tune),
|
|
);
|
|
}
|
|
|
|
/// [_buildBottomButtonBar] returns the list of buttons which are displayed in
|
|
/// the bottom button bar of the video player. If the [qualities] parameter is
|
|
/// not null, a button to select the quality of the video is added to the
|
|
/// bottom button bar. If the [isMobile] parameter is true, the bottom button
|
|
/// bar contains the default buttons from the [MaterialVideoControlsThemeData]
|
|
/// theme, if it is false it contains the default buttons from the
|
|
/// [MaterialDesktopVideoControlsThemeData] theme.
|
|
List<Widget> _buildBottomButtonBar(bool isMobile) {
|
|
if (isMobile) {
|
|
if (widget.qualities != null) {
|
|
return [
|
|
const MaterialPositionIndicator(),
|
|
const Spacer(),
|
|
_buildQualityButton(),
|
|
const MaterialFullscreenButton(),
|
|
];
|
|
}
|
|
|
|
return const [
|
|
MaterialPositionIndicator(),
|
|
Spacer(),
|
|
MaterialFullscreenButton(),
|
|
];
|
|
}
|
|
|
|
if (widget.qualities != null) {
|
|
return [
|
|
const MaterialDesktopSkipPreviousButton(),
|
|
const MaterialDesktopPlayOrPauseButton(),
|
|
const MaterialDesktopSkipNextButton(),
|
|
const MaterialDesktopVolumeButton(),
|
|
const MaterialDesktopPositionIndicator(),
|
|
const Spacer(),
|
|
_buildQualityButton(),
|
|
const MaterialDesktopFullscreenButton(),
|
|
];
|
|
}
|
|
|
|
return const [
|
|
MaterialDesktopSkipPreviousButton(),
|
|
MaterialDesktopPlayOrPauseButton(),
|
|
MaterialDesktopSkipNextButton(),
|
|
MaterialDesktopVolumeButton(),
|
|
MaterialDesktopPositionIndicator(),
|
|
Spacer(),
|
|
MaterialDesktopFullscreenButton(),
|
|
];
|
|
}
|
|
|
|
/// [_playerOpen] opens the video player with the provided [video] and sets
|
|
/// the audio track if it is provided via the [audio] parameter.
|
|
Future<void> _playerOpen(String video) async {
|
|
await player.open(
|
|
Media(video),
|
|
play: false,
|
|
);
|
|
|
|
/// Load an external audio track when it is provided via the [audio]
|
|
/// parameter.
|
|
/// See: https://github.com/media-kit/media-kit?tab=readme-ov-file#load-external-audio-track
|
|
if (widget.audio != null) {
|
|
await player.setAudioTrack(
|
|
AudioTrack.uri(
|
|
widget.audio!,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_playerOpen(widget.video);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
player.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.only(
|
|
bottom: Constants.spacingMiddle,
|
|
),
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
return Center(
|
|
child: SizedBox(
|
|
width: constraints.maxWidth,
|
|
height: constraints.maxWidth * 9.0 / 16.0,
|
|
child: MaterialDesktopVideoControlsTheme(
|
|
normal: MaterialDesktopVideoControlsThemeData(
|
|
bottomButtonBar: _buildBottomButtonBar(false),
|
|
seekBarPositionColor: Constants.primary,
|
|
seekBarThumbColor: Constants.primary,
|
|
),
|
|
fullscreen: const MaterialDesktopVideoControlsThemeData(
|
|
seekBarPositionColor: Constants.primary,
|
|
seekBarThumbColor: Constants.primary,
|
|
),
|
|
child: MaterialVideoControlsTheme(
|
|
normal: MaterialVideoControlsThemeData(
|
|
bottomButtonBar: _buildBottomButtonBar(true),
|
|
seekBarPositionColor: Constants.primary,
|
|
seekBarThumbColor: Constants.primary,
|
|
),
|
|
fullscreen: const MaterialVideoControlsThemeData(
|
|
seekBarPositionColor: Constants.primary,
|
|
seekBarThumbColor: Constants.primary,
|
|
),
|
|
child: Video(
|
|
controller: controller,
|
|
controls: kIsWeb ||
|
|
Platform.isLinux ||
|
|
Platform.isMacOS ||
|
|
Platform.isWindows
|
|
? MaterialDesktopVideoControls
|
|
: MaterialVideoControls,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|