feat: Android TV support (#503)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-09-28 21:07:49 +02:00 committed by GitHub
parent 7ab8c015b9
commit c299492d6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
168 changed files with 12019 additions and 3073 deletions

View file

@ -14,17 +14,20 @@ import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/media_playback_model.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/models/settings/video_player_settings.dart';
import 'package:fladder/providers/arguments_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/src/video_player_helper.g.dart' hide PlaybackState;
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/wrappers/players/base_player.dart';
import 'package:fladder/wrappers/players/lib_mdk.dart'
if (dart.library.html) 'package:fladder/stubs/web/lib_mdk_web.dart';
import 'package:fladder/wrappers/players/lib_mpv.dart';
import 'package:fladder/wrappers/players/native_player.dart';
import 'package:fladder/wrappers/players/player_states.dart';
class MediaControlsWrapper extends BaseAudioHandler {
class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerControlsCallback {
MediaControlsWrapper({required this.ref});
BasePlayer? _player;
@ -49,11 +52,12 @@ class MediaControlsWrapper extends BaseAudioHandler {
List<StreamSubscription> subscriptions = [];
SMTCWindows? smtc;
bool initMediaControls = false;
bool initializedWrapper = false;
Future<void> init() async {
if (!initMediaControls) {
initMediaControls = true;
if (!initializedWrapper) {
initializedWrapper = true;
VideoPlayerControlsCallback.setUp(this);
await AudioService.init(
builder: () => this,
config: const AudioServiceConfig(
@ -69,10 +73,13 @@ class MediaControlsWrapper extends BaseAudioHandler {
);
}
final player = switch (ref.read(videoPlayerSettingsProvider.select((value) => value.wantedPlayer))) {
PlayerOptions.libMDK => LibMDK(),
PlayerOptions.libMPV => LibMPV(),
};
final player = ref.read(argumentsStateProvider).leanBackMode
? NativePlayer()
: switch (ref.read(videoPlayerSettingsProvider.select((value) => value.wantedPlayer))) {
PlayerOptions.libMDK => LibMDK(),
PlayerOptions.libMPV => LibMPV(),
PlayerOptions.nativePlayer => NativePlayer(),
};
setup(player);
}
@ -93,7 +100,15 @@ class MediaControlsWrapper extends BaseAudioHandler {
_subscribePlayer();
}
Future<void> open(String url, bool play) async => _player?.open(url, play);
Future<void> loadVideo(PlaybackModel model, Duration startPosition, bool play) async {
if (_player is NativePlayer) {
final context = ref.read(localizationContextProvider);
await (_player as NativePlayer).sendPlaybackDataToNative(context, model, startPosition);
}
return _player?.loadVideo(model.media?.url ?? "", play);
}
Future<void> openPlayer(BuildContext context) async => _player?.open(context);
void _subscribePlayer() {
if (Platform.isWindows && !kIsWeb) {
@ -313,4 +328,46 @@ class MediaControlsWrapper extends BaseAudioHandler {
_player?.setSpeed(speed);
return super.setSpeed(speed);
}
//Native player calls
//
//
@override
void loadNextVideo() async {
final nextVideo = ref.read(playBackModel.select((value) => value?.nextVideo));
final buffering = ref.read(mediaPlaybackProvider.select((value) => value.buffering));
if (nextVideo != null && !buffering) ref.read(playbackModelHelper).loadNewVideo(nextVideo);
}
@override
void loadPreviousVideo() async {
final previousVideo = ref.read(playBackModel.select((value) => value?.previousVideo));
final buffering = ref.read(mediaPlaybackProvider.select((value) => value.buffering));
if (previousVideo != null && !buffering) ref.read(playbackModelHelper).loadNewVideo(previousVideo);
}
@override
void onStop() => stop();
@override
void swapAudioTrack(int value) async {
final playbackModel = ref.read(playBackModel);
final newModel = await playbackModel?.setAudio(
playbackModel.audioStreams?.firstWhere((element) => element.index == value), this);
ref.read(playBackModel.notifier).update((state) => newModel);
if (newModel != null) {
await ref.read(playbackModelHelper).shouldReload(newModel);
}
}
@override
void swapSubtitleTrack(int value) async {
final playbackModel = ref.read(playBackModel);
final newModel = await playbackModel?.setSubtitle(
playbackModel.subStreams?.firstWhere((element) => element.index == value), this);
ref.read(playBackModel.notifier).update((state) => newModel);
if (newModel != null) {
await ref.read(playbackModelHelper).shouldReload(newModel);
}
}
}