mirror of
https://github.com/nini22P/iris.git
synced 2026-09-03 06:24:07 +08:00
feat: refactor player components to use provider
This commit is contained in:
@@ -368,8 +368,8 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) {
|
||||
position: duration == Duration.zero ? Duration.zero : position,
|
||||
duration: duration,
|
||||
buffer: duration == Duration.zero ? Duration.zero : buffer,
|
||||
width: videoParams?.w?.toDouble(),
|
||||
height: videoParams?.h?.toDouble(),
|
||||
width: videoParams?.w?.toDouble() ?? 0,
|
||||
height: videoParams?.h?.toDouble() ?? 0,
|
||||
saveProgress: saveProgress,
|
||||
play: play,
|
||||
pause: pause,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:iris/models/player.dart';
|
||||
import 'package:iris/utils/logger.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void useAppLifecycle() {
|
||||
final context = useContext();
|
||||
final saveProgress = context.read<MediaPlayer>().saveProgress;
|
||||
|
||||
void useAppLifecycle(
|
||||
Future<void> Function() saveProgress,
|
||||
) {
|
||||
AppLifecycleState? appLifecycleState = useAppLifecycleState();
|
||||
|
||||
useEffect(() {
|
||||
|
||||
@@ -2,14 +2,18 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_zustand/flutter_zustand.dart';
|
||||
import 'package:iris/models/file.dart';
|
||||
import 'package:iris/models/player.dart';
|
||||
import 'package:iris/models/storages/local.dart';
|
||||
import 'package:iris/models/storages/storage.dart';
|
||||
import 'package:iris/store/use_play_queue_store.dart';
|
||||
import 'package:iris/store/use_storage_store.dart';
|
||||
import 'package:iris/utils/files_filter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
FileItem? useCover(bool isPlaying) {
|
||||
FileItem? useCover() {
|
||||
final context = useContext();
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
|
||||
final playQueue =
|
||||
usePlayQueueStore().select(context, (state) => state.playQueue);
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:iris/store/use_app_store.dart';
|
||||
import 'package:iris/store/use_player_ui_store.dart';
|
||||
import 'package:iris/utils/platform.dart';
|
||||
import 'package:iris/utils/resize_window.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class Gesture {
|
||||
@@ -55,13 +56,25 @@ class Gesture {
|
||||
}
|
||||
|
||||
Gesture useGesture({
|
||||
required MediaPlayer player,
|
||||
required void Function() showControl,
|
||||
required void Function() hideControl,
|
||||
required void Function() showProgress,
|
||||
}) {
|
||||
final context = useContext();
|
||||
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
final progress =
|
||||
context.select<MediaPlayer, ({Duration position, Duration duration})>(
|
||||
(player) => (position: player.position, duration: player.duration),
|
||||
);
|
||||
|
||||
final play = context.read<MediaPlayer>().play;
|
||||
final pause = context.read<MediaPlayer>().pause;
|
||||
final forward = context.read<MediaPlayer>().forward;
|
||||
final backward = context.read<MediaPlayer>().backward;
|
||||
final seek = context.read<MediaPlayer>().seek;
|
||||
|
||||
final aspectRatio =
|
||||
usePlayerUiStore().select(context, (state) => state.aspectRatio);
|
||||
|
||||
@@ -112,22 +125,22 @@ Gesture useGesture({
|
||||
} else {
|
||||
showProgress();
|
||||
}
|
||||
await player.forward(5);
|
||||
await forward(5);
|
||||
} else if (position < 0.25) {
|
||||
if (isShowControl) {
|
||||
showControl();
|
||||
} else {
|
||||
showProgress();
|
||||
}
|
||||
player.backward(5);
|
||||
backward(5);
|
||||
} else {
|
||||
if (player.isPlaying == true) {
|
||||
if (isPlaying == true) {
|
||||
await useAppStore().updateAutoPlay(false);
|
||||
player.pause();
|
||||
pause();
|
||||
showControl();
|
||||
} else {
|
||||
await useAppStore().updateAutoPlay(true);
|
||||
player.play();
|
||||
play();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -141,7 +154,7 @@ Gesture useGesture({
|
||||
}
|
||||
|
||||
void onLongPressStart(LongPressStartDetails details) {
|
||||
if (isTouch.value && player.isPlaying == true) {
|
||||
if (isTouch.value && isPlaying == true) {
|
||||
isLongPress.value = true;
|
||||
useAppStore().updateRate(2.0);
|
||||
}
|
||||
@@ -200,14 +213,14 @@ Gesture useGesture({
|
||||
// 水平滑动
|
||||
if (isHorizontalGesture.value && isSeeking) {
|
||||
double dx = details.delta.dx;
|
||||
int seconds = (dx * 2 + player.position.inSeconds).toInt();
|
||||
int seconds = (dx * 2 + progress.position.inSeconds).toInt();
|
||||
Duration position = Duration(
|
||||
seconds: seconds < 0
|
||||
? 0
|
||||
: seconds > player.duration.inSeconds
|
||||
? player.duration.inSeconds
|
||||
: seconds > progress.duration.inSeconds
|
||||
? progress.duration.inSeconds
|
||||
: seconds);
|
||||
player.seek(position);
|
||||
seek(position);
|
||||
if (isShowControl) {
|
||||
showControl();
|
||||
} else {
|
||||
@@ -259,7 +272,7 @@ Gesture useGesture({
|
||||
isRightGesture.value = false;
|
||||
startPosition.value = null;
|
||||
if (isSeeking) {
|
||||
await player.seek(player.position);
|
||||
await seek(progress.position);
|
||||
usePlayerUiStore().updateIsSeeking(false);
|
||||
}
|
||||
}
|
||||
@@ -272,7 +285,7 @@ Gesture useGesture({
|
||||
startPosition.value = null;
|
||||
if (isSeeking) {
|
||||
isTouch.value = false;
|
||||
await player.seek(player.position);
|
||||
await seek(progress.position);
|
||||
usePlayerUiStore().updateIsSeeking(false);
|
||||
}
|
||||
}
|
||||
@@ -285,10 +298,10 @@ Gesture useGesture({
|
||||
}
|
||||
|
||||
final cursor = useMemoized(() {
|
||||
return player.isPlaying == false
|
||||
return isPlaying == false
|
||||
? SystemMouseCursors.basic
|
||||
: SystemMouseCursors.none;
|
||||
}, [player.isPlaying]);
|
||||
}, [isPlaying]);
|
||||
|
||||
return Gesture(
|
||||
onTapDown: onTapDown,
|
||||
|
||||
@@ -16,17 +16,22 @@ import 'package:iris/utils/platform.dart';
|
||||
import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart';
|
||||
import 'package:iris/widgets/dialogs/show_open_link_dialog.dart';
|
||||
import 'package:iris/widgets/popup.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
typedef KeyboardEvent = void Function(KeyEvent event);
|
||||
|
||||
KeyboardEvent useKeyboard({
|
||||
required MediaPlayer player,
|
||||
required void Function() showControl,
|
||||
required Future<void> Function(Future<void>) showControlForHover,
|
||||
required void Function() showProgress,
|
||||
}) {
|
||||
final context = useContext();
|
||||
|
||||
final player = context.read<MediaPlayer>();
|
||||
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
|
||||
final shuffle = useAppStore().state.shuffle;
|
||||
final isFullScreen = usePlayerUiStore().state.isFullScreen;
|
||||
final isShowControl = usePlayerUiStore().state.isShowControl;
|
||||
@@ -130,7 +135,7 @@ KeyboardEvent useKeyboard({
|
||||
case LogicalKeyboardKey.space:
|
||||
case LogicalKeyboardKey.mediaPlayPause:
|
||||
showControl();
|
||||
if (player.isPlaying) {
|
||||
if (isPlaying) {
|
||||
useAppStore().updateAutoPlay(false);
|
||||
player.pause();
|
||||
} else {
|
||||
@@ -173,7 +178,7 @@ KeyboardEvent useKeyboard({
|
||||
showControlForHover(
|
||||
showPopup(
|
||||
context: context,
|
||||
child: SubtitleAndAudioTrack(player: player),
|
||||
child: SubtitleAndAudioTrack(),
|
||||
direction: PopupDirection.right,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -11,8 +11,8 @@ class MediaPlayer {
|
||||
final Duration position;
|
||||
final Duration duration;
|
||||
final Duration buffer;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final double width;
|
||||
final double height;
|
||||
final Future<void> Function() saveProgress;
|
||||
final Future<void> Function() play;
|
||||
final Future<void> Function() pause;
|
||||
|
||||
@@ -5,16 +5,17 @@ import 'package:iris/models/player.dart';
|
||||
import 'package:iris/utils/get_localizations.dart';
|
||||
import 'package:iris/utils/logger.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AudioTrackList extends HookWidget {
|
||||
const AudioTrackList({super.key, required this.player});
|
||||
|
||||
final MediaPlayer player;
|
||||
const AudioTrackList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = getLocalizations(context);
|
||||
|
||||
final player = context.read<MediaPlayer>();
|
||||
|
||||
final focusNode = useFocusNode();
|
||||
|
||||
useEffect(() {
|
||||
@@ -25,47 +26,41 @@ class AudioTrackList extends HookWidget {
|
||||
if (player is MediaKitPlayer) {
|
||||
return ListView(
|
||||
children: [
|
||||
...(player as MediaKitPlayer).audios.map(
|
||||
(audio) => ListTile(
|
||||
focusNode: (player as MediaKitPlayer).audio == audio
|
||||
? focusNode
|
||||
: null,
|
||||
title: Text(
|
||||
audio == AudioTrack.auto()
|
||||
? t.auto
|
||||
: audio == AudioTrack.no()
|
||||
? t.off
|
||||
: audio.title ?? audio.language ?? audio.id,
|
||||
style: (player as MediaKitPlayer).audio == audio
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor: (player as MediaKitPlayer).audio == audio
|
||||
? Theme.of(context).hoverColor
|
||||
: null,
|
||||
onTap: () {
|
||||
logger(
|
||||
'Set audio track: ${audio.title ?? audio.language ?? audio.id}');
|
||||
(player as MediaKitPlayer).player.setAudioTrack(audio);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
...player.audios.map(
|
||||
(audio) => ListTile(
|
||||
focusNode: player.audio == audio ? focusNode : null,
|
||||
title: Text(
|
||||
audio == AudioTrack.auto()
|
||||
? t.auto
|
||||
: audio == AudioTrack.no()
|
||||
? t.off
|
||||
: audio.title ?? audio.language ?? audio.id,
|
||||
style: player.audio == audio
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor:
|
||||
player.audio == audio ? Theme.of(context).hoverColor : null,
|
||||
onTap: () {
|
||||
logger(
|
||||
'Set audio track: ${audio.title ?? audio.language ?? audio.id}');
|
||||
player.player.setAudioTrack(audio);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (player is FvpPlayer) {
|
||||
final audios =
|
||||
(player as FvpPlayer).controller.getMediaInfo()?.audio ?? [];
|
||||
final activeAudioTracks =
|
||||
(player as FvpPlayer).controller.getActiveAudioTracks() ?? [];
|
||||
final audios = player.controller.getMediaInfo()?.audio ?? [];
|
||||
final activeAudioTracks = player.controller.getActiveAudioTracks() ?? [];
|
||||
return ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
@@ -85,7 +80,7 @@ class AudioTrackList extends HookWidget {
|
||||
activeAudioTracks.isEmpty ? Theme.of(context).hoverColor : null,
|
||||
onTap: () {
|
||||
logger('Set audio track: ${t.off}');
|
||||
(player as FvpPlayer).controller.setAudioTracks([]);
|
||||
player.controller.setAudioTracks([]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@@ -113,9 +108,7 @@ class AudioTrackList extends HookWidget {
|
||||
onTap: () {
|
||||
logger(
|
||||
'Set audio track: ${audio.metadata['title'] ?? audio.metadata['language'] ?? audios.indexOf(audio).toString()}');
|
||||
(player as FvpPlayer)
|
||||
.controller
|
||||
.setAudioTracks([audios.indexOf(audio)]);
|
||||
player.controller.setAudioTracks([audios.indexOf(audio)]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
|
||||
@@ -23,18 +23,17 @@ import 'package:iris/utils/platform.dart';
|
||||
import 'package:iris/utils/resize_window.dart';
|
||||
import 'package:iris/widgets/popup.dart';
|
||||
import 'package:iris/pages/storages/storages.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ControlBar extends HookWidget {
|
||||
const ControlBar({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.showControl,
|
||||
required this.showControlForHover,
|
||||
this.color,
|
||||
this.overlayColor,
|
||||
});
|
||||
|
||||
final MediaPlayer player;
|
||||
final void Function() showControl;
|
||||
final Future<void> Function(Future<void> callback) showControlForHover;
|
||||
final Color? color;
|
||||
@@ -44,6 +43,15 @@ class ControlBar extends HookWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final t = getLocalizations(context);
|
||||
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
|
||||
final isInitializing =
|
||||
context.select<MediaPlayer, bool>((player) => player.isInitializing);
|
||||
|
||||
final play = context.read<MediaPlayer>().play;
|
||||
final pause = context.read<MediaPlayer>().pause;
|
||||
|
||||
final rate = useAppStore().select(context, (state) => state.rate);
|
||||
final volume = useAppStore().select(context, (state) => state.volume);
|
||||
final isMuted = useAppStore().select(context, (state) => state.isMuted);
|
||||
@@ -64,14 +72,14 @@ class ControlBar extends HookWidget {
|
||||
final isSeeking =
|
||||
usePlayerUiStore().select(context, (state) => state.isSeeking);
|
||||
|
||||
final displayIsPlaying = useState(player.isPlaying);
|
||||
final displayIsPlaying = useState(isPlaying);
|
||||
|
||||
useEffect(() {
|
||||
if (!isSeeking) {
|
||||
displayIsPlaying.value = player.isPlaying;
|
||||
displayIsPlaying.value = isPlaying;
|
||||
}
|
||||
return null;
|
||||
}, [player.isPlaying]);
|
||||
}, [isPlaying]);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
@@ -92,7 +100,6 @@ class ControlBar extends HookWidget {
|
||||
Visibility(
|
||||
visible: MediaQuery.of(context).size.width < 1024 || !isDesktop,
|
||||
child: ControlBarSlider(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
color: color,
|
||||
),
|
||||
@@ -117,17 +124,17 @@ class ControlBar extends HookWidget {
|
||||
),
|
||||
onPressed: () {
|
||||
showControl();
|
||||
if (player.isPlaying == true) {
|
||||
if (isPlaying == true) {
|
||||
useAppStore().updateAutoPlay(false);
|
||||
player.pause();
|
||||
pause();
|
||||
} else {
|
||||
useAppStore().updateAutoPlay(true);
|
||||
player.play();
|
||||
play();
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(overlayColor: overlayColor),
|
||||
),
|
||||
if (player.isInitializing)
|
||||
if (isInitializing)
|
||||
SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
@@ -148,7 +155,7 @@ class ControlBar extends HookWidget {
|
||||
onPressed: () {
|
||||
showControl();
|
||||
useAppStore().updateAutoPlay(false);
|
||||
player.pause();
|
||||
pause();
|
||||
usePlayQueueStore().updateCurrentIndex(-1);
|
||||
},
|
||||
style: ButtonStyle(overlayColor: overlayColor),
|
||||
@@ -331,7 +338,6 @@ class ControlBar extends HookWidget {
|
||||
visible:
|
||||
MediaQuery.of(context).size.width >= 1024 && isDesktop,
|
||||
child: ControlBarSlider(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
color: color,
|
||||
),
|
||||
@@ -349,7 +355,10 @@ class ControlBar extends HookWidget {
|
||||
showControlForHover(
|
||||
showPopup(
|
||||
context: context,
|
||||
child: SubtitleAndAudioTrack(player: player),
|
||||
child: Provider<MediaPlayer>.value(
|
||||
value: context.read<MediaPlayer>(),
|
||||
child: const SubtitleAndAudioTrack(),
|
||||
),
|
||||
direction: PopupDirection.right,
|
||||
),
|
||||
);
|
||||
@@ -602,7 +611,10 @@ class ControlBar extends HookWidget {
|
||||
onTap: () => showControlForHover(
|
||||
showPopup(
|
||||
context: context,
|
||||
child: SubtitleAndAudioTrack(player: player),
|
||||
child: Provider<MediaPlayer>.value(
|
||||
value: context.read<MediaPlayer>(),
|
||||
child: const SubtitleAndAudioTrack(),
|
||||
),
|
||||
direction: PopupDirection.right,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,18 +5,16 @@ import 'package:iris/models/player.dart';
|
||||
import 'package:iris/store/use_app_store.dart';
|
||||
import 'package:iris/store/use_player_ui_store.dart';
|
||||
import 'package:iris/utils/format_duration_to_minutes.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ControlBarSlider extends HookWidget {
|
||||
const ControlBarSlider({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.showControl,
|
||||
this.disabled = false,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final MediaPlayer player;
|
||||
|
||||
final void Function() showControl;
|
||||
final bool disabled;
|
||||
final Color? color;
|
||||
@@ -25,6 +23,24 @@ class ControlBarSlider extends HookWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final autoPlay = useAppStore().select(context, (state) => state.autoPlay);
|
||||
|
||||
final progress = context.select<
|
||||
MediaPlayer,
|
||||
({
|
||||
Duration position,
|
||||
Duration duration,
|
||||
Duration buffer,
|
||||
})>(
|
||||
(player) => (
|
||||
position: player.position,
|
||||
duration: player.duration,
|
||||
buffer: player.buffer
|
||||
),
|
||||
);
|
||||
|
||||
final play = context.read<MediaPlayer>().play;
|
||||
final pause = context.read<MediaPlayer>().pause;
|
||||
final seek = context.read<MediaPlayer>().seek;
|
||||
|
||||
return ExcludeFocus(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
@@ -33,7 +49,7 @@ class ControlBarSlider extends HookWidget {
|
||||
Visibility(
|
||||
visible: !disabled,
|
||||
child: Text(
|
||||
formatDurationToMinutes(player.position),
|
||||
formatDurationToMinutes(progress.position),
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
height: 2,
|
||||
@@ -58,12 +74,12 @@ class ControlBarSlider extends HookWidget {
|
||||
trackHeight: 3,
|
||||
),
|
||||
child: Slider(
|
||||
value: player.buffer.inMilliseconds.toDouble() >
|
||||
player.duration.inMilliseconds.toDouble()
|
||||
value: progress.buffer.inMilliseconds.toDouble() >
|
||||
progress.duration.inMilliseconds.toDouble()
|
||||
? 0
|
||||
: player.buffer.inMilliseconds.toDouble(),
|
||||
: progress.buffer.inMilliseconds.toDouble(),
|
||||
min: 0,
|
||||
max: player.duration.inMilliseconds.toDouble(),
|
||||
max: progress.duration.inMilliseconds.toDouble(),
|
||||
onChanged: null,
|
||||
),
|
||||
),
|
||||
@@ -83,23 +99,23 @@ class ControlBarSlider extends HookWidget {
|
||||
trackHeight: 4,
|
||||
),
|
||||
child: Slider(
|
||||
value: player.position.inMilliseconds.toDouble() >
|
||||
player.duration.inMilliseconds.toDouble()
|
||||
value: progress.position.inMilliseconds.toDouble() >
|
||||
progress.duration.inMilliseconds.toDouble()
|
||||
? 0
|
||||
: player.position.inMilliseconds.toDouble(),
|
||||
: progress.position.inMilliseconds.toDouble(),
|
||||
min: 0,
|
||||
max: player.duration.inMilliseconds.toDouble(),
|
||||
max: progress.duration.inMilliseconds.toDouble(),
|
||||
onChangeStart: (value) {
|
||||
usePlayerUiStore().updateIsSeeking(true);
|
||||
player.pause();
|
||||
pause();
|
||||
},
|
||||
onChanged: (value) {
|
||||
showControl();
|
||||
player.seek(Duration(milliseconds: value.toInt()));
|
||||
seek(Duration(milliseconds: value.toInt()));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
if (autoPlay) {
|
||||
player.play();
|
||||
play();
|
||||
}
|
||||
usePlayerUiStore().updateIsSeeking(false);
|
||||
},
|
||||
@@ -111,7 +127,7 @@ class ControlBarSlider extends HookWidget {
|
||||
Visibility(
|
||||
visible: !disabled,
|
||||
child: Text(
|
||||
formatDurationToMinutes(player.duration),
|
||||
formatDurationToMinutes(progress.duration),
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
height: 2,
|
||||
|
||||
@@ -13,11 +13,11 @@ import 'package:iris/utils/format_duration_to_minutes.dart';
|
||||
import 'package:iris/utils/resize_window.dart';
|
||||
import 'package:iris/widgets/drag_aria.dart';
|
||||
import 'package:iris/widgets/title_bar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ControlsOverlay extends HookWidget {
|
||||
const ControlsOverlay({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.currentPlay,
|
||||
required this.title,
|
||||
required this.showControl,
|
||||
@@ -26,7 +26,6 @@ class ControlsOverlay extends HookWidget {
|
||||
required this.showProgress,
|
||||
});
|
||||
|
||||
final MediaPlayer player;
|
||||
final PlayQueueItem? currentPlay;
|
||||
final String title;
|
||||
final Function() showControl;
|
||||
@@ -36,6 +35,16 @@ class ControlsOverlay extends HookWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
|
||||
final progress =
|
||||
context.select<MediaPlayer, ({Duration position, Duration duration})>(
|
||||
(player) => (position: player.position, duration: player.duration),
|
||||
);
|
||||
|
||||
final saveProgress = context.read<MediaPlayer>().saveProgress;
|
||||
|
||||
final rate = useAppStore().select(context, (state) => state.rate);
|
||||
|
||||
final aspectRatio =
|
||||
@@ -46,7 +55,6 @@ class ControlsOverlay extends HookWidget {
|
||||
usePlayerUiStore().select(context, (state) => state.isShowProgress);
|
||||
|
||||
final gesture = useGesture(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
hideControl: hideControl,
|
||||
showProgress: showProgress,
|
||||
@@ -78,7 +86,7 @@ class ControlsOverlay extends HookWidget {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: MouseRegion(
|
||||
cursor: isShowControl || player.isPlaying == false
|
||||
cursor: isShowControl || isPlaying == false
|
||||
? SystemMouseCursors.basic
|
||||
: SystemMouseCursors.none,
|
||||
onHover: gesture.onHover,
|
||||
@@ -231,7 +239,6 @@ class ControlsOverlay extends HookWidget {
|
||||
bottom: -16,
|
||||
height: 32,
|
||||
child: ControlBarSlider(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
disabled: true,
|
||||
),
|
||||
@@ -274,7 +281,7 @@ class ControlsOverlay extends HookWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}',
|
||||
'${formatDurationToMinutes(progress.position)} / ${formatDurationToMinutes(progress.duration)}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
@@ -317,7 +324,7 @@ class ControlsOverlay extends HookWidget {
|
||||
actions: [const SizedBox(width: 8)],
|
||||
color: contentColor,
|
||||
overlayColor: overlayColor,
|
||||
saveProgress: () => player.saveProgress(),
|
||||
saveProgress: () => saveProgress(),
|
||||
resizeWindow: () => resizeWindow(aspectRatio),
|
||||
),
|
||||
),
|
||||
@@ -343,7 +350,6 @@ class ControlsOverlay extends HookWidget {
|
||||
child: GestureDetector(
|
||||
onTap: () => showControl(),
|
||||
child: ControlBar(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
showControlForHover: showControlForHover,
|
||||
color: contentColor,
|
||||
|
||||
@@ -22,20 +22,27 @@ import 'package:iris/utils/platform.dart';
|
||||
import 'package:iris/store/use_app_store.dart';
|
||||
import 'package:iris/store/use_play_queue_store.dart';
|
||||
import 'package:iris/utils/get_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class Player extends HookWidget {
|
||||
const Player({super.key, required this.player});
|
||||
|
||||
final MediaPlayer player;
|
||||
const Player({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = getLocalizations(context);
|
||||
|
||||
useAppLifecycle(player.saveProgress);
|
||||
final isPlaying =
|
||||
context.select<MediaPlayer, bool>((player) => player.isPlaying);
|
||||
final width = context.select<MediaPlayer, double>((player) => player.width);
|
||||
final height =
|
||||
context.select<MediaPlayer, double>((player) => player.height);
|
||||
|
||||
final cover = useCover(player.isPlaying);
|
||||
final saveProgress = context.read<MediaPlayer>().saveProgress;
|
||||
|
||||
useAppLifecycle();
|
||||
|
||||
final cover = useCover();
|
||||
|
||||
final playerUiStore = usePlayerUiStore();
|
||||
|
||||
@@ -167,7 +174,7 @@ class Player extends HookWidget {
|
||||
|
||||
final showControlForHover = useCallback((Future<void> callback) async {
|
||||
try {
|
||||
player.saveProgress();
|
||||
saveProgress();
|
||||
showControl();
|
||||
updateIsHovering(true);
|
||||
await callback;
|
||||
@@ -183,7 +190,6 @@ class Player extends HookWidget {
|
||||
}, [updateIsShowProgress, resetBottomProgressTimer]);
|
||||
|
||||
final onKeyEvent = useKeyboard(
|
||||
player: player,
|
||||
showControl: showControl,
|
||||
showControlForHover: showControlForHover,
|
||||
showProgress: showProgress,
|
||||
@@ -203,7 +209,7 @@ class Player extends HookWidget {
|
||||
windowManager.setTitle(title);
|
||||
}
|
||||
return;
|
||||
}, [title, player.isPlaying]);
|
||||
}, [title, isPlaying]);
|
||||
|
||||
useEffect(() {
|
||||
if (isShowControl || currentPlay?.file.type == ContentType.video) {
|
||||
@@ -232,18 +238,12 @@ class Player extends HookWidget {
|
||||
);
|
||||
|
||||
final videoViewSize = useMemoized(() {
|
||||
if (fit != BoxFit.none || player.width == 0 || player.height == 0) {
|
||||
if (fit != BoxFit.none || width == 0 || height == 0) {
|
||||
return MediaQuery.of(context).size;
|
||||
} else {
|
||||
return Size(player.width! / scaleFactor, player.height! / scaleFactor);
|
||||
return Size(width / scaleFactor, height / scaleFactor);
|
||||
}
|
||||
}, [
|
||||
fit,
|
||||
MediaQuery.of(context).size,
|
||||
player.width,
|
||||
player.height,
|
||||
scaleFactor
|
||||
]);
|
||||
}, [fit, MediaQuery.of(context).size, width, height, scaleFactor]);
|
||||
|
||||
final videoViewOffset = useMemoized(
|
||||
() => fit == BoxFit.none
|
||||
@@ -285,7 +285,7 @@ class Player extends HookWidget {
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
||||
if (!didPop) {
|
||||
await player.saveProgress();
|
||||
await saveProgress();
|
||||
if (!canPop.value) {
|
||||
canPop.value = true;
|
||||
if (context.mounted) {
|
||||
@@ -311,7 +311,6 @@ class Player extends HookWidget {
|
||||
height: videoViewSize.height,
|
||||
child: VideoView(
|
||||
key: ValueKey(currentPlay?.file.uri),
|
||||
player: player,
|
||||
fit: fit,
|
||||
),
|
||||
),
|
||||
@@ -330,7 +329,6 @@ class Player extends HookWidget {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: ControlsOverlay(
|
||||
player: player,
|
||||
currentPlay: currentPlay,
|
||||
title: title,
|
||||
showControl: showControl,
|
||||
|
||||
@@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:iris/hooks/player/use_fvp_player.dart';
|
||||
import 'package:iris/hooks/player/use_media_kit_player.dart';
|
||||
import 'package:iris/models/player.dart';
|
||||
import 'package:iris/models/store/app_state.dart';
|
||||
import 'package:iris/pages/player/player.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PlayerView extends HookWidget {
|
||||
const PlayerView({super.key, required this.playerBackend});
|
||||
@@ -27,7 +29,10 @@ class _MediaKitPlayerHost extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final player = useMediaKitPlayer(context);
|
||||
return Player(player: player);
|
||||
return Provider<MediaPlayer>.value(
|
||||
value: player,
|
||||
child: const Player(key: ValueKey('media_kit_player')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +42,9 @@ class _FvpPlayerHost extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final player = useFvpPlayer(context);
|
||||
return Player(player: player);
|
||||
return Provider<MediaPlayer>.value(
|
||||
value: player,
|
||||
child: const Player(key: ValueKey('fvp_player')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:iris/models/player.dart';
|
||||
import 'package:iris/pages/player/audio_track_list.dart';
|
||||
import 'package:iris/pages/player/subtitle_list.dart';
|
||||
import 'package:iris/utils/get_localizations.dart';
|
||||
@@ -16,17 +15,15 @@ class ITab {
|
||||
}
|
||||
|
||||
class SubtitleAndAudioTrack extends HookWidget {
|
||||
const SubtitleAndAudioTrack({super.key, required this.player});
|
||||
|
||||
final MediaPlayer player;
|
||||
const SubtitleAndAudioTrack({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = getLocalizations(context);
|
||||
|
||||
List<ITab> tabs = [
|
||||
ITab(title: t.subtitle, child: SubtitleList(player: player)),
|
||||
ITab(title: t.audio_track, child: AudioTrackList(player: player)),
|
||||
ITab(title: t.subtitle, child: SubtitleList()),
|
||||
ITab(title: t.audio_track, child: AudioTrackList()),
|
||||
];
|
||||
|
||||
final tabController = useTabController(initialLength: tabs.length);
|
||||
|
||||
@@ -10,20 +10,17 @@ import 'package:iris/utils/get_localizations.dart';
|
||||
import 'package:iris/utils/logger.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_stream/media_stream.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubtitleList extends HookWidget {
|
||||
const SubtitleList({super.key, required this.player});
|
||||
|
||||
final MediaPlayer player;
|
||||
const SubtitleList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = getLocalizations(context);
|
||||
|
||||
final focusNode = useFocusNode();
|
||||
|
||||
MediaStream mediaStream = MediaStream();
|
||||
final streamUrl = mediaStream.url;
|
||||
final player = context.watch<MediaPlayer>();
|
||||
|
||||
final playQueue =
|
||||
usePlayQueueStore().select(context, (state) => state.playQueue);
|
||||
@@ -34,191 +31,124 @@ class SubtitleList extends HookWidget {
|
||||
() => playQueue.indexWhere((element) => element.index == currentIndex),
|
||||
[playQueue, currentIndex]);
|
||||
|
||||
final PlayQueueItem? currentPlay = useMemoized(
|
||||
final file = useMemoized(
|
||||
() => playQueue.isEmpty || currentPlayIndex < 0
|
||||
? null
|
||||
: playQueue[currentPlayIndex],
|
||||
: playQueue[currentPlayIndex].file,
|
||||
[playQueue, currentPlayIndex]);
|
||||
|
||||
useEffect(() {
|
||||
focusNode.requestFocus();
|
||||
return () => focusNode.unfocus();
|
||||
return focusNode.unfocus;
|
||||
}, []);
|
||||
|
||||
if (player is MediaKitPlayer) {
|
||||
final activeSubtitle = context.select<MediaPlayer, SubtitleTrack>(
|
||||
(p) => p is MediaKitPlayer ? p.subtitle : SubtitleTrack.no());
|
||||
final subtitles = context.select<MediaPlayer, List<SubtitleTrack>>(
|
||||
(p) => p is MediaKitPlayer ? p.subtitles : []);
|
||||
final externalSubtitles = context.select<MediaPlayer, List<Subtitle>>(
|
||||
(p) => p is MediaKitPlayer ? p.externalSubtitles : []);
|
||||
|
||||
return ListView(
|
||||
children: [
|
||||
...(player as MediaKitPlayer).subtitles.map(
|
||||
(subtitle) => ListTile(
|
||||
focusNode: (player as MediaKitPlayer).subtitle == subtitle
|
||||
? focusNode
|
||||
: null,
|
||||
title: Text(
|
||||
subtitle == SubtitleTrack.no()
|
||||
? t.off
|
||||
: subtitle.title ?? subtitle.language ?? subtitle.id,
|
||||
style: (player as MediaKitPlayer).subtitle == subtitle
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor: (player as MediaKitPlayer).subtitle == subtitle
|
||||
? Theme.of(context).hoverColor
|
||||
: null,
|
||||
onTap: () {
|
||||
logger(
|
||||
'Set subtitle: ${subtitle.title ?? subtitle.language ?? subtitle.id}');
|
||||
(player as MediaKitPlayer)
|
||||
.player
|
||||
.setSubtitleTrack(subtitle);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
...(player as MediaKitPlayer).externalSubtitles.map(
|
||||
(subtitle) => ListTile(
|
||||
title: Text(
|
||||
subtitle.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
logger('Set external subtitle: $subtitle');
|
||||
final uri = currentPlay?.file.storageType == StorageType.ftp
|
||||
? '$streamUrl/${subtitle.uri}'
|
||||
: subtitle.uri;
|
||||
logger('External subtitle uri: $uri');
|
||||
(player as MediaKitPlayer).player.setSubtitleTrack(
|
||||
SubtitleTrack.uri(
|
||||
uri,
|
||||
title: subtitle.name,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
...subtitles.map((subtitle) {
|
||||
final bool isActive = activeSubtitle == subtitle;
|
||||
return ListTile(
|
||||
focusNode: isActive ? focusNode : null,
|
||||
title: Text(
|
||||
subtitle == SubtitleTrack.no()
|
||||
? t.off
|
||||
: subtitle.title ?? subtitle.language ?? subtitle.id,
|
||||
style: isActive
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary)
|
||||
: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
tileColor: isActive ? Theme.of(context).hoverColor : null,
|
||||
onTap: () {
|
||||
logger('Set subtitle: ${subtitle.id}');
|
||||
player.player.setSubtitleTrack(subtitle);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
...externalSubtitles.map((subtitle) {
|
||||
return ListTile(
|
||||
title: Text(subtitle.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||
onTap: () {
|
||||
logger('Set external subtitle: $subtitle');
|
||||
final mediaStream = MediaStream();
|
||||
final uri = file?.storageType == StorageType.ftp
|
||||
? '${mediaStream.url}/${subtitle.uri}'
|
||||
: subtitle.uri;
|
||||
player.player.setSubtitleTrack(
|
||||
SubtitleTrack.uri(uri, title: subtitle.name));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (player is FvpPlayer) {
|
||||
final subtitles =
|
||||
(player as FvpPlayer).controller.getMediaInfo()?.subtitle ?? [];
|
||||
final activeSubtitles =
|
||||
(player as FvpPlayer).controller.getActiveSubtitleTracks() ?? [];
|
||||
final activeSubtitles = useListenableSelector(player.controller,
|
||||
() => player.controller.getActiveSubtitleTracks() ?? []);
|
||||
final externalSubtitleIndex = useValueListenable(player.externalSubtitle);
|
||||
|
||||
final subtitles = player.controller.getMediaInfo()?.subtitle ?? [];
|
||||
final externalSubtitles = player.externalSubtitles;
|
||||
|
||||
return ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
focusNode: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.isEmpty
|
||||
selected: externalSubtitleIndex == null && activeSubtitles.isEmpty,
|
||||
focusNode: externalSubtitleIndex == null && activeSubtitles.isEmpty
|
||||
? focusNode
|
||||
: null,
|
||||
title: Text(
|
||||
t.off,
|
||||
style: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.isEmpty
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.isEmpty
|
||||
? Theme.of(context).hoverColor
|
||||
: null,
|
||||
title: Text(t.off),
|
||||
onTap: () {
|
||||
logger('Set subtitle: ${t.off}');
|
||||
(player as FvpPlayer).externalSubtitle.value = null;
|
||||
(player as FvpPlayer).controller.setSubtitleTracks([]);
|
||||
player.externalSubtitle.value = null;
|
||||
player.controller.setSubtitleTracks([]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
...subtitles.map(
|
||||
(subtitle) => ListTile(
|
||||
focusNode: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.contains(subtitles.indexOf(subtitle))
|
||||
? focusNode
|
||||
: null,
|
||||
title: Text(
|
||||
subtitle.metadata['title'] ??
|
||||
subtitle.metadata['language'] ??
|
||||
subtitle.index.toString(),
|
||||
style: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.contains(subtitles.indexOf(subtitle))
|
||||
? TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor: (player as FvpPlayer).externalSubtitle.value == null &&
|
||||
activeSubtitles.contains(subtitles.indexOf(subtitle))
|
||||
? Theme.of(context).hoverColor
|
||||
: null,
|
||||
...subtitles.map((subtitle) {
|
||||
final int index = subtitles.indexOf(subtitle);
|
||||
final bool isActive = externalSubtitleIndex == null &&
|
||||
activeSubtitles.contains(index);
|
||||
return ListTile(
|
||||
selected: isActive,
|
||||
focusNode: isActive ? focusNode : null,
|
||||
title: Text(subtitle.metadata['title'] ??
|
||||
subtitle.metadata['language'] ??
|
||||
subtitle.index.toString()),
|
||||
onTap: () {
|
||||
logger(
|
||||
'Set subtitle: ${subtitle.metadata['title'] ?? subtitle.metadata['language'] ?? subtitle.index.toString()}');
|
||||
(player as FvpPlayer).externalSubtitle.value = null;
|
||||
(player as FvpPlayer)
|
||||
.controller
|
||||
.setSubtitleTracks([subtitles.indexOf(subtitle)]);
|
||||
player.externalSubtitle.value = null;
|
||||
player.controller.setSubtitleTracks([index]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
...(player as FvpPlayer).externalSubtitles.map(
|
||||
(subtitle) => ListTile(
|
||||
focusNode: (player as FvpPlayer).externalSubtitle.value ==
|
||||
(player as FvpPlayer)
|
||||
.externalSubtitles
|
||||
.indexOf(subtitle)
|
||||
? focusNode
|
||||
: null,
|
||||
title: (player as FvpPlayer).externalSubtitle.value ==
|
||||
(player as FvpPlayer)
|
||||
.externalSubtitles
|
||||
.indexOf(subtitle)
|
||||
? Text(
|
||||
subtitle.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
: Text(
|
||||
subtitle.name,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
tileColor: (player as FvpPlayer).externalSubtitle.value ==
|
||||
(player as FvpPlayer)
|
||||
.externalSubtitles
|
||||
.indexOf(subtitle)
|
||||
? Theme.of(context).hoverColor
|
||||
: null,
|
||||
onTap: () {
|
||||
logger('Set external subtitle: $subtitle');
|
||||
(player as FvpPlayer).externalSubtitle.value =
|
||||
(player as FvpPlayer)
|
||||
.externalSubtitles
|
||||
.indexOf(subtitle);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
...externalSubtitles.map((subtitle) {
|
||||
final int index = externalSubtitles.indexOf(subtitle);
|
||||
final bool isActive = externalSubtitleIndex == index;
|
||||
return ListTile(
|
||||
selected: isActive,
|
||||
focusNode: isActive ? focusNode : null,
|
||||
title: Text(subtitle.name),
|
||||
onTap: () {
|
||||
player.externalSubtitle.value = index;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,21 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:iris/models/player.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoView extends HookWidget {
|
||||
const VideoView({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.fit,
|
||||
});
|
||||
|
||||
final MediaPlayer player;
|
||||
final BoxFit fit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final player = context.read<MediaPlayer>();
|
||||
|
||||
return switch (player) {
|
||||
MediaKitPlayer player => Video(
|
||||
controller: player.controller,
|
||||
|
||||
Reference in New Issue
Block a user