mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feat: Implement next-up screen for native player (#533)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
311b647286
commit
29b1c2e633
25 changed files with 782 additions and 203 deletions
|
|
@ -30,6 +30,7 @@ import 'package:fladder/screens/details_screens/episode_detail_screen.dart';
|
|||
import 'package:fladder/screens/details_screens/season_detail_screen.dart';
|
||||
import 'package:fladder/screens/library_search/library_search_screen.dart';
|
||||
import 'package:fladder/screens/photo_viewer/photo_viewer_screen.dart';
|
||||
import 'package:fladder/src/video_player_helper.g.dart' show SimpleItemModel;
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
|
|
@ -233,6 +234,17 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
);
|
||||
}
|
||||
|
||||
SimpleItemModel toSimpleItem(BuildContext? context) {
|
||||
return SimpleItemModel(
|
||||
id: id,
|
||||
title: title,
|
||||
subTitle: context != null ? label(context) : null,
|
||||
overview: overview.summary,
|
||||
logoUrl: images?.logo?.path,
|
||||
primaryPoster: images?.primary?.path ?? getPosters?.primary?.path ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
FladderItemType get type => switch (this) {
|
||||
MovieModel _ => FladderItemType.movie,
|
||||
SeriesModel _ => FladderItemType.series,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
|
|
@ -42,6 +43,11 @@ final pigeonPlayerSettingsSyncProvider = Provider<void>((ref) {
|
|||
),
|
||||
),
|
||||
themeColor: color,
|
||||
autoNextType: switch (value.nextVideoType) {
|
||||
AutoNextType.off => pigeon.AutoNextType.off,
|
||||
AutoNextType.static => pigeon.AutoNextType.static,
|
||||
AutoNextType.smart => pigeon.AutoNextType.smart,
|
||||
},
|
||||
skipBackward: (userData?.userSettings?.skipBackDuration ?? const Duration(seconds: 15)).inMilliseconds,
|
||||
skipForward: (userData?.userSettings?.skipForwardDuration ?? const Duration(seconds: 30)).inMilliseconds,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -377,39 +377,42 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
|||
"${context.localized.noVideoPlayerOptions}\n${context.localized.mdkExperimental}"),
|
||||
},
|
||||
),
|
||||
if (videoSettings.wantedPlayer != PlayerOptions.nativePlayer) ...[
|
||||
Column(
|
||||
children: [
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsAutoNextTitle),
|
||||
subLabel: Text(context.localized.settingsAutoNextDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
videoPlayerSettingsProvider.select(
|
||||
(value) => value.nextVideoType.label(context),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.settingsAutoNextTitle),
|
||||
subLabel: Text(context.localized.settingsAutoNextDesc),
|
||||
trailing: EnumBox(
|
||||
current: ref.watch(
|
||||
videoPlayerSettingsProvider.select(
|
||||
(value) => value.nextVideoType.label(context),
|
||||
),
|
||||
itemBuilder: (context) => AutoNextType.values
|
||||
.map(
|
||||
(entry) => ItemActionButton(
|
||||
label: Text(entry.label(context)),
|
||||
action: () => ref.read(videoPlayerSettingsProvider.notifier).state =
|
||||
videoSettings.copyWith(nextVideoType: entry),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
itemBuilder: (context) => AutoNextType.values
|
||||
.map(
|
||||
(entry) => ItemActionButton(
|
||||
label: Text(entry.label(context)),
|
||||
action: () => ref.read(videoPlayerSettingsProvider.notifier).state =
|
||||
videoSettings.copyWith(nextVideoType: entry),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
AnimatedFadeSize(
|
||||
child: switch (ref.watch(videoPlayerSettingsProvider.select((value) => value.nextVideoType))) {
|
||||
AutoNextType.smart => SettingsMessageBox(AutoNextType.smart.desc(context)),
|
||||
AutoNextType.static => SettingsMessageBox(AutoNextType.static.desc(context)),
|
||||
_ => const SizedBox.shrink(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!AdaptiveLayout.of(context).isDesktop && !kIsWeb && !ref.read(argumentsStateProvider).htpcMode)
|
||||
),
|
||||
AnimatedFadeSize(
|
||||
child: switch (ref.watch(videoPlayerSettingsProvider.select((value) => value.nextVideoType))) {
|
||||
AutoNextType.smart => SettingsMessageBox(AutoNextType.smart.desc(context)),
|
||||
AutoNextType.static => SettingsMessageBox(AutoNextType.static.desc(context)),
|
||||
_ => const SizedBox.shrink(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (videoSettings.wantedPlayer != PlayerOptions.nativePlayer) ...[
|
||||
if (!AdaptiveLayout.of(context).isDesktop &&
|
||||
!kIsWeb &&
|
||||
!ref.read(argumentsStateProvider).htpcMode &&
|
||||
videoSettings.wantedPlayer != PlayerOptions.nativePlayer)
|
||||
SettingsListTile(
|
||||
label: Text(context.localized.playerSettingsOrientationTitle),
|
||||
subLabel: Text(context.localized.playerSettingsOrientationDesc),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ bool _deepEquals(Object? a, Object? b) {
|
|||
}
|
||||
|
||||
|
||||
enum AutoNextType {
|
||||
off,
|
||||
static,
|
||||
smart,
|
||||
}
|
||||
|
||||
enum SegmentType {
|
||||
commercial,
|
||||
preview,
|
||||
|
|
@ -50,6 +56,7 @@ class PlayerSettings {
|
|||
this.themeColor,
|
||||
required this.skipForward,
|
||||
required this.skipBackward,
|
||||
required this.autoNextType,
|
||||
});
|
||||
|
||||
bool enableTunneling;
|
||||
|
|
@ -62,6 +69,8 @@ class PlayerSettings {
|
|||
|
||||
int skipBackward;
|
||||
|
||||
AutoNextType autoNextType;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
enableTunneling,
|
||||
|
|
@ -69,6 +78,7 @@ class PlayerSettings {
|
|||
themeColor,
|
||||
skipForward,
|
||||
skipBackward,
|
||||
autoNextType,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +93,7 @@ class PlayerSettings {
|
|||
themeColor: result[2] as int?,
|
||||
skipForward: result[3]! as int,
|
||||
skipBackward: result[4]! as int,
|
||||
autoNextType: result[5]! as AutoNextType,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -112,14 +123,17 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is SegmentType) {
|
||||
} else if (value is AutoNextType) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is SegmentSkip) {
|
||||
} else if (value is SegmentType) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is PlayerSettings) {
|
||||
} else if (value is SegmentSkip) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is PlayerSettings) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
|
|
@ -131,11 +145,14 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
switch (type) {
|
||||
case 129:
|
||||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : SegmentType.values[value];
|
||||
return value == null ? null : AutoNextType.values[value];
|
||||
case 130:
|
||||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : SegmentSkip.values[value];
|
||||
return value == null ? null : SegmentType.values[value];
|
||||
case 131:
|
||||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : SegmentSkip.values[value];
|
||||
case 132:
|
||||
return PlayerSettings.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
|
|
|
|||
|
|
@ -47,12 +47,75 @@ enum MediaSegmentType {
|
|||
outro,
|
||||
}
|
||||
|
||||
class PlayableData {
|
||||
PlayableData({
|
||||
class SimpleItemModel {
|
||||
SimpleItemModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.subTitle,
|
||||
this.overview,
|
||||
this.logoUrl,
|
||||
required this.primaryPoster,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
String title;
|
||||
|
||||
String? subTitle;
|
||||
|
||||
String? overview;
|
||||
|
||||
String? logoUrl;
|
||||
|
||||
String primaryPoster;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
id,
|
||||
title,
|
||||
subTitle,
|
||||
overview,
|
||||
logoUrl,
|
||||
primaryPoster,
|
||||
];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList(); }
|
||||
|
||||
static SimpleItemModel decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SimpleItemModel(
|
||||
id: result[0]! as String,
|
||||
title: result[1]! as String,
|
||||
subTitle: result[2] as String?,
|
||||
overview: result[3] as String?,
|
||||
logoUrl: result[4] as String?,
|
||||
primaryPoster: result[5]! as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SimpleItemModel || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(encode(), other.encode());
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hashAll(_toList())
|
||||
;
|
||||
}
|
||||
|
||||
class PlayableData {
|
||||
PlayableData({
|
||||
required this.currentItem,
|
||||
required this.description,
|
||||
required this.startPosition,
|
||||
required this.defaultAudioTrack,
|
||||
|
|
@ -67,13 +130,7 @@ class PlayableData {
|
|||
required this.url,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
String title;
|
||||
|
||||
String? subTitle;
|
||||
|
||||
String? logoUrl;
|
||||
SimpleItemModel currentItem;
|
||||
|
||||
String description;
|
||||
|
||||
|
|
@ -93,18 +150,15 @@ class PlayableData {
|
|||
|
||||
List<MediaSegment> segments;
|
||||
|
||||
String? previousVideo;
|
||||
SimpleItemModel? previousVideo;
|
||||
|
||||
String? nextVideo;
|
||||
SimpleItemModel? nextVideo;
|
||||
|
||||
String url;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
id,
|
||||
title,
|
||||
subTitle,
|
||||
logoUrl,
|
||||
currentItem,
|
||||
description,
|
||||
startPosition,
|
||||
defaultAudioTrack,
|
||||
|
|
@ -126,22 +180,19 @@ class PlayableData {
|
|||
static PlayableData decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return PlayableData(
|
||||
id: result[0]! as String,
|
||||
title: result[1]! as String,
|
||||
subTitle: result[2] as String?,
|
||||
logoUrl: result[3] as String?,
|
||||
description: result[4]! as String,
|
||||
startPosition: result[5]! as int,
|
||||
defaultAudioTrack: result[6]! as int,
|
||||
audioTracks: (result[7] as List<Object?>?)!.cast<AudioTrack>(),
|
||||
defaultSubtrack: result[8]! as int,
|
||||
subtitleTracks: (result[9] as List<Object?>?)!.cast<SubtitleTrack>(),
|
||||
trickPlayModel: result[10] as TrickPlayModel?,
|
||||
chapters: (result[11] as List<Object?>?)!.cast<Chapter>(),
|
||||
segments: (result[12] as List<Object?>?)!.cast<MediaSegment>(),
|
||||
previousVideo: result[13] as String?,
|
||||
nextVideo: result[14] as String?,
|
||||
url: result[15]! as String,
|
||||
currentItem: result[0]! as SimpleItemModel,
|
||||
description: result[1]! as String,
|
||||
startPosition: result[2]! as int,
|
||||
defaultAudioTrack: result[3]! as int,
|
||||
audioTracks: (result[4] as List<Object?>?)!.cast<AudioTrack>(),
|
||||
defaultSubtrack: result[5]! as int,
|
||||
subtitleTracks: (result[6] as List<Object?>?)!.cast<SubtitleTrack>(),
|
||||
trickPlayModel: result[7] as TrickPlayModel?,
|
||||
chapters: (result[8] as List<Object?>?)!.cast<Chapter>(),
|
||||
segments: (result[9] as List<Object?>?)!.cast<MediaSegment>(),
|
||||
previousVideo: result[10] as SimpleItemModel?,
|
||||
nextVideo: result[11] as SimpleItemModel?,
|
||||
url: result[12]! as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -596,30 +647,33 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
} else if (value is MediaSegmentType) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is PlayableData) {
|
||||
} else if (value is SimpleItemModel) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is MediaSegment) {
|
||||
} else if (value is PlayableData) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is AudioTrack) {
|
||||
} else if (value is MediaSegment) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SubtitleTrack) {
|
||||
} else if (value is AudioTrack) {
|
||||
buffer.putUint8(133);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is Chapter) {
|
||||
} else if (value is SubtitleTrack) {
|
||||
buffer.putUint8(134);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is TrickPlayModel) {
|
||||
} else if (value is Chapter) {
|
||||
buffer.putUint8(135);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is StartResult) {
|
||||
} else if (value is TrickPlayModel) {
|
||||
buffer.putUint8(136);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is PlaybackState) {
|
||||
} else if (value is StartResult) {
|
||||
buffer.putUint8(137);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is PlaybackState) {
|
||||
buffer.putUint8(138);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
|
|
@ -632,20 +686,22 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : MediaSegmentType.values[value];
|
||||
case 130:
|
||||
return PlayableData.decode(readValue(buffer)!);
|
||||
return SimpleItemModel.decode(readValue(buffer)!);
|
||||
case 131:
|
||||
return MediaSegment.decode(readValue(buffer)!);
|
||||
return PlayableData.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
return AudioTrack.decode(readValue(buffer)!);
|
||||
return MediaSegment.decode(readValue(buffer)!);
|
||||
case 133:
|
||||
return SubtitleTrack.decode(readValue(buffer)!);
|
||||
return AudioTrack.decode(readValue(buffer)!);
|
||||
case 134:
|
||||
return Chapter.decode(readValue(buffer)!);
|
||||
return SubtitleTrack.decode(readValue(buffer)!);
|
||||
case 135:
|
||||
return TrickPlayModel.decode(readValue(buffer)!);
|
||||
return Chapter.decode(readValue(buffer)!);
|
||||
case 136:
|
||||
return StartResult.decode(readValue(buffer)!);
|
||||
return TrickPlayModel.decode(readValue(buffer)!);
|
||||
case 137:
|
||||
return StartResult.decode(readValue(buffer)!);
|
||||
case 138:
|
||||
return PlaybackState.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
|
|
|
|||
|
|
@ -100,15 +100,12 @@ class NativePlayer extends BasePlayer implements VideoPlayerListenerCallback {
|
|||
Duration startPosition,
|
||||
) async {
|
||||
final playableData = PlayableData(
|
||||
id: model.item.id,
|
||||
title: model.item.title,
|
||||
subTitle: context != null ? model.item.label(context) : "",
|
||||
logoUrl: model.item.getPosters?.logo?.path,
|
||||
currentItem: model.item.toSimpleItem(context),
|
||||
startPosition: startPosition.inMilliseconds,
|
||||
description: model.item.overview.summary,
|
||||
defaultAudioTrack: model.mediaStreams?.defaultAudioStreamIndex ?? 1,
|
||||
nextVideo: model.nextVideo?.name,
|
||||
previousVideo: model.previousVideo?.name,
|
||||
nextVideo: model.nextVideo?.toSimpleItem(context),
|
||||
previousVideo: model.previousVideo?.toSimpleItem(context),
|
||||
audioTracks: model.audioStreams
|
||||
?.map(
|
||||
(audio) => AudioTrack(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue