mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-13 09:20:31 -07:00
feat: Android TV support (#503)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
7ab8c015b9
commit
c299492d6d
168 changed files with 12019 additions and 3073 deletions
|
|
@ -23,7 +23,8 @@ abstract class BasePlayer {
|
|||
GlobalKey? controlsKey,
|
||||
});
|
||||
Future<void> dispose();
|
||||
Future<void> open(String url, bool play);
|
||||
Future<void> open(BuildContext context);
|
||||
Future<void> loadVideo(String url, bool play);
|
||||
Future<void> seek(Duration position);
|
||||
Future<void> play();
|
||||
Future<void> setVolume(double volume);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:video_player/video_player.dart';
|
|||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/screens/video_player/video_player.dart' as video_screen;
|
||||
import 'package:fladder/wrappers/players/base_player.dart';
|
||||
import 'package:fladder/wrappers/players/player_states.dart';
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ class LibMDK extends BasePlayer {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> open(String url, bool play) async {
|
||||
Future<void> loadVideo(String url, bool play) async {
|
||||
if (_controller != null) {
|
||||
_controller?.dispose();
|
||||
}
|
||||
|
|
@ -95,6 +96,13 @@ class LibMDK extends BasePlayer {
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> open(BuildContext context) async => Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const video_screen.VideoPlayer(),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> pause() async => _controller?.pause();
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fladder/models/playback/playback_model.dart';
|
|||
import 'package:fladder/models/settings/subtitle_settings_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
|
||||
import 'package:fladder/screens/video_player/video_player.dart' as video_screen;
|
||||
import 'package:fladder/util/subtitle_position_calculator.dart';
|
||||
import 'package:fladder/wrappers/players/base_player.dart';
|
||||
import 'package:fladder/wrappers/players/player_states.dart';
|
||||
|
|
@ -82,11 +83,18 @@ class LibMPV extends BasePlayer {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> open(String url, bool play) async {
|
||||
Future<void> loadVideo(String url, bool play) async {
|
||||
await _player?.open(mpv.Media(url), play: play);
|
||||
return setState(lastState.update(buffering: true));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> open(BuildContext context) async => Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const video_screen.VideoPlayer(),
|
||||
),
|
||||
);
|
||||
|
||||
List<mpv.SubtitleTrack> get subTracks => _player?.state.tracks.subtitle ?? [];
|
||||
mpv.SubtitleTrack get subtitleTrack => _player?.state.track.subtitle ?? mpv.SubtitleTrack.no();
|
||||
|
||||
|
|
|
|||
167
lib/wrappers/players/native_player.dart
Normal file
167
lib/wrappers/players/native_player.dart
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/models/settings/video_player_settings.dart';
|
||||
import 'package:fladder/src/video_player_helper.g.dart';
|
||||
import 'package:fladder/wrappers/players/base_player.dart';
|
||||
import 'package:fladder/wrappers/players/player_states.dart';
|
||||
|
||||
class NativePlayer extends BasePlayer implements VideoPlayerListenerCallback {
|
||||
final player = VideoPlayerApi();
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
return NativeVideoActivity().disposeActivity();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init(VideoPlayerSettingsModel settings) async => VideoPlayerListenerCallback.setUp(this);
|
||||
|
||||
@override
|
||||
Future<void> loop(bool loop) {
|
||||
return player.setLooping(loop);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadVideo(String url, bool play) async => player.open(url, play);
|
||||
|
||||
@override
|
||||
Future<void> open(BuildContext newContext) async => NativeVideoActivity().launchActivity();
|
||||
|
||||
@override
|
||||
Future<void> pause() {
|
||||
return player.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() => player.play();
|
||||
|
||||
@override
|
||||
Future<void> playOrPause() async {
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seek(Duration position) {
|
||||
return player.seekTo(position.inMilliseconds);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> setAudioTrack(AudioStreamModel? model, PlaybackModel playbackModel) async {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setSpeed(double speed) async {}
|
||||
|
||||
@override
|
||||
Future<int> setSubtitleTrack(SubStreamModel? model, PlaybackModel playbackModel) async {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setVolume(double volume) async {
|
||||
return player.setVolume(volume);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
return player.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? subtitles(bool showOverlay, {GlobalKey<State<StatefulWidget>>? controlsKey}) => null;
|
||||
|
||||
@override
|
||||
Widget? videoWidget(Key key, BoxFit fit) => null;
|
||||
|
||||
@override
|
||||
void onPlaybackStateChanged(PlaybackState state) {
|
||||
lastState = lastState.update(
|
||||
playing: state.playing,
|
||||
position: Duration(milliseconds: state.position),
|
||||
buffer: Duration(milliseconds: state.buffered),
|
||||
buffering: state.buffering,
|
||||
);
|
||||
_stateController.add(lastState);
|
||||
}
|
||||
|
||||
final StreamController<PlayerState> _stateController = StreamController.broadcast();
|
||||
|
||||
@override
|
||||
Stream<PlayerState> get stateStream => _stateController.stream;
|
||||
|
||||
Future<void> sendPlaybackDataToNative(
|
||||
BuildContext? context,
|
||||
PlaybackModel model,
|
||||
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,
|
||||
startPosition: startPosition.inMilliseconds,
|
||||
description: model.item.overview.summary,
|
||||
defaultAudioTrack: model.mediaStreams?.defaultAudioStreamIndex ?? 1,
|
||||
nextVideo: model.nextVideo?.name,
|
||||
previousVideo: model.previousVideo?.name,
|
||||
audioTracks: model.audioStreams
|
||||
?.map(
|
||||
(audio) => AudioTrack(
|
||||
name: audio.displayTitle,
|
||||
languageCode: audio.language,
|
||||
codec: audio.codec,
|
||||
index: audio.index,
|
||||
external: false,
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
defaultSubtrack: model.mediaStreams?.defaultSubStreamIndex ?? 1,
|
||||
subtitleTracks: model.subStreams
|
||||
?.map(
|
||||
(sub) => SubtitleTrack(
|
||||
name: sub.displayTitle,
|
||||
languageCode: sub.language,
|
||||
codec: sub.codec,
|
||||
index: sub.index,
|
||||
external: sub.isExternal,
|
||||
url: sub.url,
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
segments: model.mediaSegments?.segments
|
||||
.map(
|
||||
(e) => MediaSegment(
|
||||
type: MediaSegmentType.values.firstWhere((element) => element.name == e.type.name),
|
||||
name: context != null ? e.type.label(context) : e.type.name,
|
||||
start: e.start.inMilliseconds,
|
||||
end: e.end.inMilliseconds,
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
trickPlayModel: model.trickPlay != null
|
||||
? TrickPlayModel(
|
||||
width: model.trickPlay!.width,
|
||||
height: model.trickPlay!.height,
|
||||
tileWidth: model.trickPlay!.tileWidth,
|
||||
tileHeight: model.trickPlay!.tileHeight,
|
||||
thumbnailCount: model.trickPlay!.thumbnailCount,
|
||||
interval: model.trickPlay!.interval.inMilliseconds,
|
||||
images: model.trickPlay?.images ?? [])
|
||||
: null,
|
||||
chapters: model.chapters
|
||||
?.map((e) => Chapter(name: e.name, url: e.imageUrl, time: e.startPosition.inMilliseconds))
|
||||
.toList() ??
|
||||
[],
|
||||
url: model.media?.url ?? "",
|
||||
);
|
||||
player.sendPlayableModel(playableData);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue