feature: Added LibMDK video player backend (#162)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2024-11-22 18:53:31 +01:00 committed by GitHub
parent 6e32018183
commit da354437e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1499 additions and 1006 deletions

View file

@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/providers/session_info_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/util/localization_helper.dart';
Future<void> showVideoPlaybackInformation(BuildContext context) {
return showDialog(
@ -19,6 +22,7 @@ class _VideoPlaybackInformation extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final playbackModel = ref.watch(playBackModel);
final sessionInfo = ref.watch(sessionInfoProvider);
final backend = ref.read(videoPlayerProvider.select((value) => value.backend));
return Dialog(
child: Padding(
padding: const EdgeInsets.all(12.0),
@ -27,47 +31,81 @@ class _VideoPlaybackInformation extends ConsumerWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Playback information", style: Theme.of(context).textTheme.titleMedium),
Text("Player info", style: Theme.of(context).textTheme.titleMedium),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4).copyWith(top: 4),
child: Opacity(
opacity: 0.80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('backend: '), Text(backend?.label(context) ?? context.localized.unknown)],
)
].addPadding(const EdgeInsets.symmetric(vertical: 3)),
),
),
),
const Divider(),
...[
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('type: '), Text(playbackModel.label ?? "")],
),
if (sessionInfo.transCodeInfo != null) ...[
const SizedBox(height: 6),
Text("Transcoding", style: Theme.of(context).textTheme.titleMedium),
if (sessionInfo.transCodeInfo?.transcodeReasons?.isNotEmpty == true)
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('reason: '), Text(sessionInfo.transCodeInfo?.transcodeReasons.toString() ?? "")],
),
if (sessionInfo.transCodeInfo?.completionPercentage != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('transcode progress: '),
Text("${sessionInfo.transCodeInfo?.completionPercentage?.toStringAsFixed(2)} %")
Text("Playback information", style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4).copyWith(top: 4),
child: Opacity(
opacity: 0.8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('type: '), Text(playbackModel.label ?? "")],
),
if (sessionInfo.transCodeInfo != null) ...[
Text("Transcoding", style: Theme.of(context).textTheme.titleMedium),
if (sessionInfo.transCodeInfo?.transcodeReasons?.isNotEmpty == true)
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('reason: '),
Text(sessionInfo.transCodeInfo?.transcodeReasons.toString() ?? "")
],
),
if (sessionInfo.transCodeInfo?.completionPercentage != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('transcode progress: '),
Text("${sessionInfo.transCodeInfo?.completionPercentage?.toStringAsFixed(2)} %")
],
),
if (sessionInfo.transCodeInfo?.container != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('container: '),
Text(sessionInfo.transCodeInfo!.container.toString())
],
),
],
),
if (sessionInfo.transCodeInfo?.container != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('container: '), Text(sessionInfo.transCodeInfo!.container.toString())],
),
],
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('resolution: '), Text(playbackModel?.item.streamModel?.resolutionText ?? "")],
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('resolution: '),
Text(playbackModel?.item.streamModel?.resolutionText ?? "")
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('container: '),
Text(playbackModel?.playbackInfo?.mediaSources?.firstOrNull?.container ?? "")
],
)
].addPadding(const EdgeInsets.symmetric(vertical: 3)),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('container: '),
Text(playbackModel?.playbackInfo?.mediaSources?.firstOrNull?.container ?? "")
],
),
].addPadding(const EdgeInsets.symmetric(vertical: 3))
),
],
),
),

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/screens/video_player/components/video_player_chapters.dart';
@ -9,8 +8,7 @@ import 'package:fladder/screens/video_player/components/video_player_queue.dart'
class ChapterButton extends ConsumerWidget {
final Duration position;
final Player player;
const ChapterButton({super.key, required this.position, required this.player});
const ChapterButton({super.key, required this.position});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -22,9 +20,9 @@ class ChapterButton extends ConsumerWidget {
context,
chapters: currentChapters,
currentPosition: position,
onChapterTapped: (chapter) => player.seek(
chapter.startPosition,
),
onChapterTapped: (chapter) => ref.read(videoPlayerProvider).seek(
chapter.startPosition,
),
);
},
icon: const Icon(

View file

@ -447,13 +447,13 @@ class _SimpleControls extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(videoPlayerProvider.select((value) => value.controller?.player));
final player = ref.watch(videoPlayerProvider);
final isPlaying = ref.watch(mediaPlaybackProvider.select((value) => value.playing));
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton.filledTonal(
onPressed: () => player?.playOrPause(),
onPressed: () => player.playOrPause(),
icon: Icon(isPlaying ? IconsaxBold.pause : IconsaxBold.play),
),
if (skip != null)

View file

@ -12,6 +12,7 @@ import 'package:fladder/models/playback/direct_playback_model.dart';
import 'package:fladder/models/playback/offline_playback_model.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/models/playback/transcode_playback_model.dart';
import 'package:fladder/models/settings/video_player_settings.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
@ -375,15 +376,16 @@ Future<void> showSubSelection(BuildContext context) {
children: [
Text(context.localized.subtitle),
const Spacer(),
IconButton.outlined(
onPressed: () {
Navigator.pop(context);
showSubtitleControls(
context: context,
label: context.localized.subtitleConfiguration,
);
},
icon: const Icon(Icons.display_settings_rounded))
if (player.backend == PlayerOptions.libMPV)
IconButton.outlined(
onPressed: () {
Navigator.pop(context);
showSubtitleControls(
context: context,
label: context.localized.subtitleConfiguration,
);
},
icon: const Icon(Icons.display_settings_rounded))
],
),
children: playbackModel?.subStreams?.mapIndexed(
@ -459,7 +461,7 @@ Future<void> showPlaybackSpeed(BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return Consumer(
builder: (context, ref, child) {
final player = ref.watch(videoPlayerProvider.select((value) => value.player));
final player = ref.watch(videoPlayerProvider);
final lastSpeed = ref.watch(playbackRateProvider);
return SimpleDialog(
contentPadding: const EdgeInsets.only(top: 8, bottom: 24),
@ -484,7 +486,7 @@ Future<void> showPlaybackSpeed(BuildContext context) {
divisions: 39,
onChanged: (value) {
ref.read(playbackRateProvider.notifier).state = value;
player?.setRate(value);
player.setSpeed(value);
},
),
),

View file

@ -122,7 +122,7 @@ class _ChapterProgressSliderState extends ConsumerState<VideoProgressBar> {
setState(() {
onHoverStart = true;
});
widget.wasPlayingChanged.call(player.player?.state.playing ?? false);
widget.wasPlayingChanged.call(player.lastState?.playing ?? false);
player.pause();
},
onChanged: (e) {

View file

@ -1,64 +0,0 @@
import 'dart:async';
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:fladder/models/settings/subtitle_settings_model.dart';
class VideoSubtitles extends ConsumerStatefulWidget {
final VideoController controller;
final bool overLayed;
const VideoSubtitles({
required this.controller,
this.overLayed = false,
super.key,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _VideoSubtitlesState();
}
class _VideoSubtitlesState extends ConsumerState<VideoSubtitles> {
late List<String> subtitle = widget.controller.player.state.subtitle;
StreamSubscription<List<String>>? subscription;
@override
void initState() {
subscription = widget.controller.player.stream.subtitle.listen((value) {
setState(() {
subtitle = value;
});
});
super.initState();
}
@override
void dispose() {
subscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final settings = ref.watch(subtitleSettingsProvider);
final padding = MediaQuery.of(context).padding;
final text = [
for (final line in subtitle)
if (line.trim().isNotEmpty) line.trim(),
].join('\n');
if (widget.controller.player.platform?.configuration.libass ?? false) {
return const IgnorePointer(child: SizedBox());
} else {
return SubtitleText(
subModel: settings,
padding: padding,
offset: (widget.overLayed ? 0.5 : settings.verticalOffset),
text: text,
);
}
}
}