refactor: fix naming

This commit is contained in:
edde746
2025-12-02 16:53:54 +01:00
parent a122fb048e
commit 6d9a56fa73
45 changed files with 403 additions and 403 deletions
+18 -18
View File
@@ -17,7 +17,7 @@
/// import 'package:flutter_application_1/mpv/mpv.dart';
///
/// // Create a player
/// final player = MpvPlayer();
/// final player = Player();
///
/// // Configure player properties
/// await player.setProperty('hwdec', 'auto');
@@ -25,7 +25,7 @@
/// await player.setAudioPassthrough(true);
///
/// // Open and play media
/// await player.open(MpvMedia('https://example.com/video.mp4'));
/// await player.open(Media('https://example.com/video.mp4'));
///
/// // Listen to state changes
/// player.streams.position.listen((position) {
@@ -33,7 +33,7 @@
/// });
///
/// // Display video
/// MpvVideo(
/// Video(
/// player: player,
/// controls: (context) => MyCustomControls(),
/// )
@@ -44,26 +44,26 @@
library;
// Import for type alias
import 'src/models/mpv_track_selection.dart' as track_selection;
import 'src/models/track_selection.dart' as track_selection;
// Player
export 'src/player/mpv_player.dart';
export 'src/player/mpv_player_state.dart';
export 'src/player/mpv_player_streams.dart';
export 'src/player/mpv_player_stub.dart';
export 'src/player/player.dart';
export 'src/player/player_state.dart';
export 'src/player/player_streams.dart';
export 'src/player/player_stub.dart';
// Models
export 'src/models/mpv_media.dart';
export 'src/models/mpv_audio_device.dart';
export 'src/models/mpv_audio_track.dart';
export 'src/models/mpv_subtitle_track.dart';
export 'src/models/mpv_tracks.dart';
export 'src/models/mpv_track_selection.dart';
export 'src/models/mpv_log.dart';
export 'src/models/mpv_log_level.dart';
export 'src/models/media.dart';
export 'src/models/audio_device.dart';
export 'src/models/audio_track.dart';
export 'src/models/subtitle_track.dart';
export 'src/models/tracks.dart';
export 'src/models/track_selection.dart';
export 'src/models/player_log.dart';
export 'src/models/player_log_level.dart';
// Video
export 'src/video/mpv_video.dart';
export 'src/video/video.dart';
// Type alias for compatibility (must be after exports)
typedef MpvTrack = track_selection.MpvTrackSelection;
typedef Track = track_selection.TrackSelection;
@@ -1,26 +1,26 @@
/// Represents an audio output device.
class MpvAudioDevice {
class AudioDevice {
/// Unique identifier for the device.
final String name;
/// Human-readable description of the device.
final String description;
const MpvAudioDevice({
const AudioDevice({
required this.name,
this.description = '',
});
/// Default/auto audio device.
static const auto = MpvAudioDevice(name: 'auto', description: 'Auto');
static const auto = AudioDevice(name: 'auto', description: 'Auto');
@override
String toString() => 'MpvAudioDevice($name, $description)';
String toString() => 'AudioDevice($name, $description)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MpvAudioDevice &&
other is AudioDevice &&
runtimeType == other.runtimeType &&
name == other.name;
@@ -1,5 +1,5 @@
/// Represents an audio track in the media.
class MpvAudioTrack {
class AudioTrack {
/// Unique identifier for the track.
final String id;
@@ -30,7 +30,7 @@ class MpvAudioTrack {
/// Whether this track is forced.
final bool isForced;
const MpvAudioTrack({
const AudioTrack({
required this.id,
this.title,
this.language,
@@ -43,10 +43,10 @@ class MpvAudioTrack {
});
/// Auto-select track.
static const auto = MpvAudioTrack(id: 'auto', title: 'Auto');
static const auto = AudioTrack(id: 'auto', title: 'Auto');
/// Disable audio.
static const off = MpvAudioTrack(id: 'no', title: 'Off');
static const off = AudioTrack(id: 'no', title: 'Off');
/// Returns a display name for the track.
String get displayName {
@@ -56,12 +56,12 @@ class MpvAudioTrack {
}
@override
String toString() => 'MpvAudioTrack($id, $displayName)';
String toString() => 'AudioTrack($id, $displayName)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MpvAudioTrack &&
other is AudioTrack &&
runtimeType == other.runtimeType &&
id == other.id;
@@ -1,5 +1,5 @@
/// Represents a media source for the MPV player.
class MpvMedia {
/// Represents a media source for the player.
class Media {
/// The URI of the media (file path, HTTP URL, etc.).
final String uri;
@@ -9,19 +9,19 @@ class MpvMedia {
/// Optional start position for playback.
final Duration? start;
const MpvMedia(
const Media(
this.uri, {
this.headers,
this.start,
});
@override
String toString() => 'MpvMedia($uri)';
String toString() => 'Media($uri)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MpvMedia &&
other is Media &&
runtimeType == other.runtimeType &&
uri == other.uri &&
start == other.start;
@@ -1,30 +0,0 @@
import 'mpv_audio_track.dart';
import 'mpv_subtitle_track.dart';
/// Represents the currently selected tracks.
class MpvTrackSelection {
/// Currently selected audio track.
final MpvAudioTrack? audio;
/// Currently selected subtitle track.
final MpvSubtitleTrack? subtitle;
const MpvTrackSelection({
this.audio,
this.subtitle,
});
/// Creates a copy with the given fields replaced.
MpvTrackSelection copyWith({
MpvAudioTrack? audio,
MpvSubtitleTrack? subtitle,
}) {
return MpvTrackSelection(
audio: audio ?? this.audio,
subtitle: subtitle ?? this.subtitle,
);
}
@override
String toString() => 'MpvTrackSelection(audio: $audio, subtitle: $subtitle)';
}
@@ -1,9 +1,9 @@
import 'mpv_log_level.dart';
import 'player_log_level.dart';
/// A log entry from the MPV player.
class MpvLog {
/// A log entry from the player.
class PlayerLog {
/// The log level of this message.
final MpvLogLevel level;
final PlayerLogLevel level;
/// The prefix/category of the log message (e.g., 'cplayer', 'ffmpeg').
final String prefix;
@@ -11,7 +11,7 @@ class MpvLog {
/// The log message text.
final String text;
const MpvLog({
const PlayerLog({
required this.level,
required this.prefix,
required this.text,
@@ -1,5 +1,5 @@
/// Log level for MPV player messages.
enum MpvLogLevel {
/// Log level for player messages.
enum PlayerLogLevel {
/// No logging.
none,
@@ -1,5 +1,5 @@
/// Represents a subtitle track in the media.
class MpvSubtitleTrack {
class SubtitleTrack {
/// Unique identifier for the track.
final String id;
@@ -24,7 +24,7 @@ class MpvSubtitleTrack {
/// URI of external subtitle file (if isExternal is true).
final String? uri;
const MpvSubtitleTrack({
const SubtitleTrack({
required this.id,
this.title,
this.language,
@@ -36,12 +36,12 @@ class MpvSubtitleTrack {
});
/// Create a subtitle track from an external URI.
factory MpvSubtitleTrack.uri(
factory SubtitleTrack.uri(
String uri, {
String? title,
String? language,
}) {
return MpvSubtitleTrack(
return SubtitleTrack(
id: 'external:$uri',
title: title,
language: language,
@@ -51,10 +51,10 @@ class MpvSubtitleTrack {
}
/// Auto-select track.
static const auto = MpvSubtitleTrack(id: 'auto', title: 'Auto');
static const auto = SubtitleTrack(id: 'auto', title: 'Auto');
/// Disable subtitles.
static const off = MpvSubtitleTrack(id: 'no', title: 'Off');
static const off = SubtitleTrack(id: 'no', title: 'Off');
/// Returns a display name for the track.
String get displayName {
@@ -65,12 +65,12 @@ class MpvSubtitleTrack {
}
@override
String toString() => 'MpvSubtitleTrack($id, $displayName)';
String toString() => 'SubtitleTrack($id, $displayName)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MpvSubtitleTrack &&
other is SubtitleTrack &&
runtimeType == other.runtimeType &&
id == other.id;
+30
View File
@@ -0,0 +1,30 @@
import 'audio_track.dart';
import 'subtitle_track.dart';
/// Represents the currently selected tracks.
class TrackSelection {
/// Currently selected audio track.
final AudioTrack? audio;
/// Currently selected subtitle track.
final SubtitleTrack? subtitle;
const TrackSelection({
this.audio,
this.subtitle,
});
/// Creates a copy with the given fields replaced.
TrackSelection copyWith({
AudioTrack? audio,
SubtitleTrack? subtitle,
}) {
return TrackSelection(
audio: audio ?? this.audio,
subtitle: subtitle ?? this.subtitle,
);
}
@override
String toString() => 'TrackSelection(audio: $audio, subtitle: $subtitle)';
}
@@ -1,25 +1,25 @@
import 'mpv_audio_track.dart';
import 'mpv_subtitle_track.dart';
import 'audio_track.dart';
import 'subtitle_track.dart';
/// Container for all available tracks in the media.
class MpvTracks {
class Tracks {
/// Available audio tracks.
final List<MpvAudioTrack> audio;
final List<AudioTrack> audio;
/// Available subtitle tracks.
final List<MpvSubtitleTrack> subtitle;
final List<SubtitleTrack> subtitle;
const MpvTracks({
const Tracks({
this.audio = const [],
this.subtitle = const [],
});
/// Creates a copy with the given fields replaced.
MpvTracks copyWith({
List<MpvAudioTrack>? audio,
List<MpvSubtitleTrack>? subtitle,
Tracks copyWith({
List<AudioTrack>? audio,
List<SubtitleTrack>? subtitle,
}) {
return MpvTracks(
return Tracks(
audio: audio ?? this.audio,
subtitle: subtitle ?? this.subtitle,
);
@@ -27,5 +27,5 @@ class MpvTracks {
@override
String toString() =>
'MpvTracks(audio: ${audio.length}, subtitle: ${subtitle.length})';
'Tracks(audio: ${audio.length}, subtitle: ${subtitle.length})';
}
@@ -1,5 +0,0 @@
import 'mpv_player_native.dart';
/// Android implementation of MpvPlayer.
/// Inherits all functionality from MpvPlayerNative.
class MpvPlayerAndroid extends MpvPlayerNative {}
-5
View File
@@ -1,5 +0,0 @@
import 'mpv_player_native.dart';
/// iOS implementation of MpvPlayer.
/// Inherits all functionality from MpvPlayerNative.
class MpvPlayerIOS extends MpvPlayerNative {}
-5
View File
@@ -1,5 +0,0 @@
import 'mpv_player_native.dart';
/// macOS implementation of MpvPlayer.
/// Inherits all functionality from MpvPlayerNative.
class MpvPlayerMacOS extends MpvPlayerNative {}
@@ -1,27 +1,27 @@
import 'dart:io' show Platform;
import '../models/mpv_audio_device.dart';
import '../models/mpv_media.dart';
import '../models/mpv_audio_track.dart';
import '../models/mpv_subtitle_track.dart';
import 'mpv_player_android.dart';
import 'mpv_player_ios.dart';
import 'mpv_player_linux.dart';
import 'mpv_player_macos.dart';
import 'mpv_player_state.dart';
import 'mpv_player_streams.dart';
import 'mpv_player_stub.dart';
import 'mpv_player_windows.dart';
import '../models/audio_device.dart';
import '../models/media.dart';
import '../models/audio_track.dart';
import '../models/subtitle_track.dart';
import 'player_android.dart';
import 'player_ios.dart';
import 'player_linux.dart';
import 'player_macos.dart';
import 'player_state.dart';
import 'player_streams.dart';
import 'player_stub.dart';
import 'player_windows.dart';
/// Abstract interface for the MPV player.
/// Abstract interface for the video player.
///
/// This interface defines all playback control methods, state access,
/// and reactive streams for the video player.
///
/// Example usage:
/// ```dart
/// final player = MpvPlayer();
/// await player.open(MpvMedia('https://example.com/video.mp4'));
/// final player = Player();
/// await player.open(Media('https://example.com/video.mp4'));
/// await player.play();
///
/// // Configure player properties
@@ -36,16 +36,16 @@ import 'mpv_player_windows.dart';
/// // Access current state
/// print('Playing: ${player.state.playing}');
/// ```
abstract class MpvPlayer {
abstract class Player {
/// Current synchronous state snapshot.
///
/// Use this for immediate state access in UI.
MpvPlayerState get state;
PlayerState get state;
/// Reactive streams for state changes.
///
/// Use these for reactive UI updates.
MpvPlayerStreams get streams;
PlayerStreams get streams;
/// Texture ID for Flutter's Texture widget (video rendering).
///
@@ -61,7 +61,7 @@ abstract class MpvPlayer {
///
/// [media] - The media source to open.
/// [play] - Whether to start playback immediately (default: true).
Future<void> open(MpvMedia media, {bool play = true});
Future<void> open(Media media, {bool play = true});
/// Start or resume playback.
Future<void> play();
@@ -83,12 +83,12 @@ abstract class MpvPlayer {
// ============================================
/// Select an audio track.
Future<void> selectAudioTrack(MpvAudioTrack track);
Future<void> selectAudioTrack(AudioTrack track);
/// Select a subtitle track.
///
/// Pass [MpvSubtitleTrack.off] to disable subtitles.
Future<void> selectSubtitleTrack(MpvSubtitleTrack track);
/// Pass [SubtitleTrack.off] to disable subtitles.
Future<void> selectSubtitleTrack(SubtitleTrack track);
/// Add an external subtitle track.
///
@@ -120,7 +120,7 @@ abstract class MpvPlayer {
/// Set the audio output device.
///
/// [device] - The audio device to use.
Future<void> setAudioDevice(MpvAudioDevice device);
Future<void> setAudioDevice(AudioDevice device);
// ============================================
// MPV Properties (Advanced)
@@ -194,31 +194,31 @@ abstract class MpvPlayer {
// Factory
// ============================================
/// Creates a new MPV player instance.
/// Creates a new player instance.
///
/// Returns a platform-specific implementation:
/// - macOS: [MpvPlayerMacOS] using MPVKit with Metal rendering
/// - iOS: [MpvPlayerIOS] using MPVKit with Metal rendering
/// - Android: [MpvPlayerAndroid] using libmpv
/// - Windows: [MpvPlayerWindows] using libmpv with native window embedding
/// - Linux: [MpvPlayerLinux] using libmpv with OpenGL rendering via GtkGLArea
/// - Other platforms: [MpvPlayerStub] (placeholder)
factory MpvPlayer() {
/// - macOS: [PlayerMacOS] using MPVKit with Metal rendering
/// - iOS: [PlayerIOS] using MPVKit with Metal rendering
/// - Android: [PlayerAndroid] using libmpv
/// - Windows: [PlayerWindows] using libmpv with native window embedding
/// - Linux: [PlayerLinux] using libmpv with OpenGL rendering via GtkGLArea
/// - Other platforms: [PlayerStub] (placeholder)
factory Player() {
if (Platform.isMacOS) {
return MpvPlayerMacOS();
return PlayerMacOS();
}
if (Platform.isIOS) {
return MpvPlayerIOS();
return PlayerIOS();
}
if (Platform.isAndroid) {
return MpvPlayerAndroid();
return PlayerAndroid();
}
if (Platform.isWindows) {
return MpvPlayerWindows();
return PlayerWindows();
}
if (Platform.isLinux) {
return MpvPlayerLinux();
return PlayerLinux();
}
return MpvPlayerStub();
return PlayerStub();
}
}
+5
View File
@@ -0,0 +1,5 @@
import 'player_native.dart';
/// Android implementation of Player.
/// Inherits all functionality from PlayerNative.
class PlayerAndroid extends PlayerNative {}
+5
View File
@@ -0,0 +1,5 @@
import 'player_native.dart';
/// iOS implementation of Player.
/// Inherits all functionality from PlayerNative.
class PlayerIOS extends PlayerNative {}
@@ -1,14 +1,14 @@
import 'package:flutter/services.dart';
import 'mpv_player_native.dart';
import 'player_native.dart';
/// Linux implementation of [MpvPlayer].
/// Linux implementation of [Player].
///
/// Uses libmpv with OpenGL rendering via GtkGLArea.
/// The mpv video is rendered to a GtkGLArea positioned behind
/// the Flutter view using a GtkOverlay, with transparent regions
/// in the Flutter UI allowing the video to show through.
class MpvPlayerLinux extends MpvPlayerNative {
class PlayerLinux extends PlayerNative {
static const _methodChannel = MethodChannel('com.plezy/mpv_player');
@override
+5
View File
@@ -0,0 +1,5 @@
import 'player_native.dart';
/// macOS implementation of Player.
/// Inherits all functionality from PlayerNative.
class PlayerMacOS extends PlayerNative {}
@@ -2,32 +2,32 @@ import 'dart:async';
import 'package:flutter/services.dart';
import '../models/mpv_audio_device.dart';
import '../models/mpv_audio_track.dart';
import '../models/mpv_log.dart';
import '../models/mpv_media.dart';
import '../models/mpv_subtitle_track.dart';
import '../models/mpv_track_selection.dart';
import '../models/mpv_tracks.dart';
import 'mpv_player.dart';
import 'mpv_player_state.dart';
import 'mpv_player_streams.dart';
import '../models/audio_device.dart';
import '../models/audio_track.dart';
import '../models/player_log.dart';
import '../models/media.dart';
import '../models/subtitle_track.dart';
import '../models/track_selection.dart';
import '../models/tracks.dart';
import 'player.dart';
import 'player_state.dart';
import 'player_streams.dart';
/// Shared native implementation of [MpvPlayer] for iOS and macOS.
/// Shared native implementation of [Player] for iOS and macOS.
/// Uses MPVKit via platform channels with Metal rendering.
class MpvPlayerNative implements MpvPlayer {
class PlayerNative implements Player {
static const _methodChannel = MethodChannel('com.plezy/mpv_player');
static const _eventChannel = EventChannel('com.plezy/mpv_player/events');
MpvPlayerState _state = const MpvPlayerState();
PlayerState _state = const PlayerState();
@override
MpvPlayerState get state => _state;
PlayerState get state => _state;
late final MpvPlayerStreams _streams;
late final PlayerStreams _streams;
@override
MpvPlayerStreams get streams => _streams;
PlayerStreams get streams => _streams;
@override
int? get textureId => null; // Uses direct Metal layer, not Flutter texture
@@ -41,21 +41,21 @@ class MpvPlayerNative implements MpvPlayer {
final _bufferController = StreamController<Duration>.broadcast();
final _volumeController = StreamController<double>.broadcast();
final _rateController = StreamController<double>.broadcast();
final _tracksController = StreamController<MpvTracks>.broadcast();
final _trackController = StreamController<MpvTrackSelection>.broadcast();
final _logController = StreamController<MpvLog>.broadcast();
final _tracksController = StreamController<Tracks>.broadcast();
final _trackController = StreamController<TrackSelection>.broadcast();
final _logController = StreamController<PlayerLog>.broadcast();
final _errorController = StreamController<String>.broadcast();
final _audioDeviceController = StreamController<MpvAudioDevice>.broadcast();
final _audioDeviceController = StreamController<AudioDevice>.broadcast();
final _audioDevicesController =
StreamController<List<MpvAudioDevice>>.broadcast();
StreamController<List<AudioDevice>>.broadcast();
StreamSubscription? _eventSubscription;
bool _disposed = false;
bool _initialized = false;
bool _isVisible = false;
MpvPlayerNative() {
_streams = MpvPlayerStreams(
PlayerNative() {
_streams = PlayerStreams(
playing: _playingController.stream,
completed: _completedController.stream,
buffering: _bufferingController.stream,
@@ -195,9 +195,9 @@ class MpvPlayerNative implements MpvPlayer {
}
}
MpvTracks _parseTrackList(List trackList) {
final audioTracks = <MpvAudioTrack>[];
final subtitleTracks = <MpvSubtitleTrack>[];
Tracks _parseTrackList(List trackList) {
final audioTracks = <AudioTrack>[];
final subtitleTracks = <SubtitleTrack>[];
for (final track in trackList) {
if (track is! Map) continue;
@@ -206,7 +206,7 @@ class MpvPlayerNative implements MpvPlayer {
final id = track['id']?.toString() ?? '';
if (type == 'audio') {
audioTracks.add(MpvAudioTrack(
audioTracks.add(AudioTrack(
id: id,
title: track['title'] as String?,
language: track['lang'] as String?,
@@ -216,7 +216,7 @@ class MpvPlayerNative implements MpvPlayer {
isDefault: track['default'] as bool? ?? false,
));
} else if (type == 'sub') {
subtitleTracks.add(MpvSubtitleTrack(
subtitleTracks.add(SubtitleTrack(
id: id,
title: track['title'] as String?,
language: track['lang'] as String?,
@@ -227,15 +227,15 @@ class MpvPlayerNative implements MpvPlayer {
}
}
return MpvTracks(audio: audioTracks, subtitle: subtitleTracks);
return Tracks(audio: audioTracks, subtitle: subtitleTracks);
}
void _updateSelectedAudioTrack(dynamic trackId) {
final id = trackId?.toString();
MpvAudioTrack? selectedTrack;
AudioTrack? selectedTrack;
if (id != null && id != 'no') {
selectedTrack = _state.tracks.audio.cast<MpvAudioTrack?>().firstWhere(
selectedTrack = _state.tracks.audio.cast<AudioTrack?>().firstWhere(
(t) => t?.id == id,
orElse: () => null,
);
@@ -249,11 +249,11 @@ class MpvPlayerNative implements MpvPlayer {
void _updateSelectedSubtitleTrack(dynamic trackId) {
final id = trackId?.toString();
MpvSubtitleTrack? selectedTrack;
SubtitleTrack? selectedTrack;
if (id != null && id != 'no') {
selectedTrack =
_state.tracks.subtitle.cast<MpvSubtitleTrack?>().firstWhere(
_state.tracks.subtitle.cast<SubtitleTrack?>().firstWhere(
(t) => t?.id == id,
orElse: () => null,
);
@@ -272,7 +272,7 @@ class MpvPlayerNative implements MpvPlayer {
final result = await _methodChannel.invokeMethod<bool>('initialize');
_initialized = result == true;
if (!_initialized) {
throw Exception('Failed to initialize MPV player');
throw Exception('Failed to initialize player');
}
// Subscribe to MPV properties
@@ -300,7 +300,7 @@ class MpvPlayerNative implements MpvPlayer {
void _checkDisposed() {
if (_disposed) {
throw StateError('MpvPlayer has been disposed');
throw StateError('Player has been disposed');
}
}
@@ -309,7 +309,7 @@ class MpvPlayerNative implements MpvPlayer {
// ============================================
@override
Future<void> open(MpvMedia media, {bool play = true}) async {
Future<void> open(Media media, {bool play = true}) async {
_checkDisposed();
await _ensureInitialized();
@@ -376,13 +376,13 @@ class MpvPlayerNative implements MpvPlayer {
// ============================================
@override
Future<void> selectAudioTrack(MpvAudioTrack track) async {
Future<void> selectAudioTrack(AudioTrack track) async {
_checkDisposed();
await setProperty('aid', track.id);
}
@override
Future<void> selectSubtitleTrack(MpvSubtitleTrack track) async {
Future<void> selectSubtitleTrack(SubtitleTrack track) async {
_checkDisposed();
await setProperty('sid', track.id);
}
@@ -418,7 +418,7 @@ class MpvPlayerNative implements MpvPlayer {
}
@override
Future<void> setAudioDevice(MpvAudioDevice device) async {
Future<void> setAudioDevice(AudioDevice device) async {
_checkDisposed();
await setProperty('audio-device', device.name);
}
@@ -1,12 +1,12 @@
import '../models/mpv_audio_device.dart';
import '../models/mpv_tracks.dart';
import '../models/mpv_track_selection.dart';
import '../models/audio_device.dart';
import '../models/tracks.dart';
import '../models/track_selection.dart';
/// Immutable snapshot of the current player state.
///
/// This class provides synchronous access to the player's current state.
/// For reactive updates, use [MpvPlayerStreams].
class MpvPlayerState {
/// For reactive updates, use [PlayerStreams].
class PlayerState {
/// Whether playback is currently active.
final bool playing;
@@ -32,10 +32,10 @@ class MpvPlayerState {
final double rate;
/// Available tracks in the media.
final MpvTracks tracks;
final Tracks tracks;
/// Currently selected tracks.
final MpvTrackSelection track;
final TrackSelection track;
/// Audio delay/sync offset in seconds.
final double audioDelay;
@@ -47,12 +47,12 @@ class MpvPlayerState {
final bool audioPassthrough;
/// Current audio output device.
final MpvAudioDevice audioDevice;
final AudioDevice audioDevice;
/// Available audio output devices.
final List<MpvAudioDevice> audioDevices;
final List<AudioDevice> audioDevices;
const MpvPlayerState({
const PlayerState({
this.playing = false,
this.completed = false,
this.buffering = false,
@@ -61,17 +61,17 @@ class MpvPlayerState {
this.buffer = Duration.zero,
this.volume = 100.0,
this.rate = 1.0,
this.tracks = const MpvTracks(),
this.track = const MpvTrackSelection(),
this.tracks = const Tracks(),
this.track = const TrackSelection(),
this.audioDelay = 0.0,
this.subtitleDelay = 0.0,
this.audioPassthrough = false,
this.audioDevice = MpvAudioDevice.auto,
this.audioDevice = AudioDevice.auto,
this.audioDevices = const [],
});
/// Creates a copy with the given fields replaced.
MpvPlayerState copyWith({
PlayerState copyWith({
bool? playing,
bool? completed,
bool? buffering,
@@ -80,15 +80,15 @@ class MpvPlayerState {
Duration? buffer,
double? volume,
double? rate,
MpvTracks? tracks,
MpvTrackSelection? track,
Tracks? tracks,
TrackSelection? track,
double? audioDelay,
double? subtitleDelay,
bool? audioPassthrough,
MpvAudioDevice? audioDevice,
List<MpvAudioDevice>? audioDevices,
AudioDevice? audioDevice,
List<AudioDevice>? audioDevices,
}) {
return MpvPlayerState(
return PlayerState(
playing: playing ?? this.playing,
completed: completed ?? this.completed,
buffering: buffering ?? this.buffering,
@@ -109,5 +109,5 @@ class MpvPlayerState {
@override
String toString() =>
'MpvPlayerState(playing: $playing, position: $position, duration: $duration)';
'PlayerState(playing: $playing, position: $position, duration: $duration)';
}
@@ -1,13 +1,13 @@
import '../models/mpv_audio_device.dart';
import '../models/mpv_log.dart';
import '../models/mpv_tracks.dart';
import '../models/mpv_track_selection.dart';
import '../models/audio_device.dart';
import '../models/player_log.dart';
import '../models/tracks.dart';
import '../models/track_selection.dart';
/// Reactive streams for player state changes.
///
/// Subscribe to these streams to receive updates when the player state changes.
/// For synchronous state access, use [MpvPlayerState].
class MpvPlayerStreams {
/// For synchronous state access, use [PlayerState].
class PlayerStreams {
/// Stream of playing state changes.
final Stream<bool> playing;
@@ -33,24 +33,24 @@ class MpvPlayerStreams {
final Stream<double> rate;
/// Stream of available tracks updates.
final Stream<MpvTracks> tracks;
final Stream<Tracks> tracks;
/// Stream of track selection changes.
final Stream<MpvTrackSelection> track;
final Stream<TrackSelection> track;
/// Stream of log messages from MPV.
final Stream<MpvLog> log;
/// Stream of log messages from the player.
final Stream<PlayerLog> log;
/// Stream of error messages.
final Stream<String> error;
/// Stream of audio device changes.
final Stream<MpvAudioDevice> audioDevice;
final Stream<AudioDevice> audioDevice;
/// Stream of available audio devices.
final Stream<List<MpvAudioDevice>> audioDevices;
final Stream<List<AudioDevice>> audioDevices;
const MpvPlayerStreams({
const PlayerStreams({
required this.playing,
required this.completed,
required this.buffering,
@@ -1,31 +1,31 @@
import 'dart:async';
import '../models/mpv_audio_device.dart';
import '../models/mpv_media.dart';
import '../models/mpv_audio_track.dart';
import '../models/mpv_subtitle_track.dart';
import '../models/mpv_tracks.dart';
import '../models/mpv_track_selection.dart';
import '../models/mpv_log.dart';
import 'mpv_player.dart';
import 'mpv_player_state.dart';
import 'mpv_player_streams.dart';
import '../models/audio_device.dart';
import '../models/media.dart';
import '../models/audio_track.dart';
import '../models/subtitle_track.dart';
import '../models/tracks.dart';
import '../models/track_selection.dart';
import '../models/player_log.dart';
import 'player.dart';
import 'player_state.dart';
import 'player_streams.dart';
/// Stub implementation of [MpvPlayer].
/// Stub implementation of [Player].
///
/// This implementation creates all the necessary stream controllers and
/// state management, but throws [UnimplementedError] for all playback
/// methods. It serves as a foundation for platform-specific implementations.
class MpvPlayerStub implements MpvPlayer {
MpvPlayerState _state = const MpvPlayerState();
class PlayerStub implements Player {
PlayerState _state = const PlayerState();
@override
MpvPlayerState get state => _state;
PlayerState get state => _state;
late final MpvPlayerStreams _streams;
late final PlayerStreams _streams;
@override
MpvPlayerStreams get streams => _streams;
PlayerStreams get streams => _streams;
@override
int? get textureId => null; // Set by platform implementation
@@ -39,18 +39,18 @@ class MpvPlayerStub implements MpvPlayer {
final _bufferController = StreamController<Duration>.broadcast();
final _volumeController = StreamController<double>.broadcast();
final _rateController = StreamController<double>.broadcast();
final _tracksController = StreamController<MpvTracks>.broadcast();
final _trackController = StreamController<MpvTrackSelection>.broadcast();
final _logController = StreamController<MpvLog>.broadcast();
final _tracksController = StreamController<Tracks>.broadcast();
final _trackController = StreamController<TrackSelection>.broadcast();
final _logController = StreamController<PlayerLog>.broadcast();
final _errorController = StreamController<String>.broadcast();
final _audioDeviceController = StreamController<MpvAudioDevice>.broadcast();
final _audioDeviceController = StreamController<AudioDevice>.broadcast();
final _audioDevicesController =
StreamController<List<MpvAudioDevice>>.broadcast();
StreamController<List<AudioDevice>>.broadcast();
bool _disposed = false;
MpvPlayerStub() {
_streams = MpvPlayerStreams(
PlayerStub() {
_streams = PlayerStreams(
playing: _playingController.stream,
completed: _completedController.stream,
buffering: _bufferingController.stream,
@@ -70,7 +70,7 @@ class MpvPlayerStub implements MpvPlayer {
void _checkDisposed() {
if (_disposed) {
throw StateError('MpvPlayer has been disposed');
throw StateError('Player has been disposed');
}
}
@@ -79,21 +79,21 @@ class MpvPlayerStub implements MpvPlayer {
// ============================================
@override
Future<void> open(MpvMedia media, {bool play = true}) async {
Future<void> open(Media media, {bool play = true}) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.open() is not yet implemented');
throw UnimplementedError('Player.open() is not yet implemented');
}
@override
Future<void> play() async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.play() is not yet implemented');
throw UnimplementedError('Player.play() is not yet implemented');
}
@override
Future<void> pause() async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.pause() is not yet implemented');
throw UnimplementedError('Player.pause() is not yet implemented');
}
@override
@@ -109,13 +109,13 @@ class MpvPlayerStub implements MpvPlayer {
@override
Future<void> stop() async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.stop() is not yet implemented');
throw UnimplementedError('Player.stop() is not yet implemented');
}
@override
Future<void> seek(Duration position) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.seek() is not yet implemented');
throw UnimplementedError('Player.seek() is not yet implemented');
}
// ============================================
@@ -123,17 +123,17 @@ class MpvPlayerStub implements MpvPlayer {
// ============================================
@override
Future<void> selectAudioTrack(MpvAudioTrack track) async {
Future<void> selectAudioTrack(AudioTrack track) async {
_checkDisposed();
throw UnimplementedError(
'MpvPlayer.selectAudioTrack() is not yet implemented');
'Player.selectAudioTrack() is not yet implemented');
}
@override
Future<void> selectSubtitleTrack(MpvSubtitleTrack track) async {
Future<void> selectSubtitleTrack(SubtitleTrack track) async {
_checkDisposed();
throw UnimplementedError(
'MpvPlayer.selectSubtitleTrack() is not yet implemented');
'Player.selectSubtitleTrack() is not yet implemented');
}
@override
@@ -145,7 +145,7 @@ class MpvPlayerStub implements MpvPlayer {
}) async {
_checkDisposed();
throw UnimplementedError(
'MpvPlayer.addSubtitleTrack() is not yet implemented');
'Player.addSubtitleTrack() is not yet implemented');
}
// ============================================
@@ -155,20 +155,20 @@ class MpvPlayerStub implements MpvPlayer {
@override
Future<void> setVolume(double volume) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.setVolume() is not yet implemented');
throw UnimplementedError('Player.setVolume() is not yet implemented');
}
@override
Future<void> setRate(double rate) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.setRate() is not yet implemented');
throw UnimplementedError('Player.setRate() is not yet implemented');
}
@override
Future<void> setAudioDevice(MpvAudioDevice device) async {
Future<void> setAudioDevice(AudioDevice device) async {
_checkDisposed();
throw UnimplementedError(
'MpvPlayer.setAudioDevice() is not yet implemented');
'Player.setAudioDevice() is not yet implemented');
}
// ============================================
@@ -178,19 +178,19 @@ class MpvPlayerStub implements MpvPlayer {
@override
Future<void> setProperty(String name, String value) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.setProperty() is not yet implemented');
throw UnimplementedError('Player.setProperty() is not yet implemented');
}
@override
Future<String?> getProperty(String name) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.getProperty() is not yet implemented');
throw UnimplementedError('Player.getProperty() is not yet implemented');
}
@override
Future<void> command(List<String> args) async {
_checkDisposed();
throw UnimplementedError('MpvPlayer.command() is not yet implemented');
throw UnimplementedError('Player.command() is not yet implemented');
}
// ============================================
@@ -201,7 +201,7 @@ class MpvPlayerStub implements MpvPlayer {
Future<void> setAudioPassthrough(bool enabled) async {
_checkDisposed();
throw UnimplementedError(
'MpvPlayer.setAudioPassthrough() is not yet implemented');
'Player.setAudioPassthrough() is not yet implemented');
}
// ============================================
@@ -1,13 +1,13 @@
import 'package:flutter/services.dart';
import 'mpv_player_native.dart';
import 'player_native.dart';
/// Windows implementation of [MpvPlayer].
/// Windows implementation of [Player].
///
/// Uses libmpv via platform channels with native window embedding.
/// The mpv video window is positioned behind the Flutter window,
/// with transparent regions allowing the video to show through.
class MpvPlayerWindows extends MpvPlayerNative {
class PlayerWindows extends PlayerNative {
static const _methodChannel = MethodChannel('com.plezy/mpv_player');
@override
@@ -15,7 +15,7 @@ class MpvPlayerWindows extends MpvPlayerNative {
/// Updates the video window position and size.
///
/// This is called by [MpvVideo] when the widget layout changes.
/// This is called by [Video] when the widget layout changes.
Future<void> setVideoRect({
required int left,
required int top,
@@ -2,28 +2,28 @@ import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import '../player/mpv_player.dart';
import '../player/mpv_player_linux.dart';
import '../player/mpv_player_windows.dart';
import '../player/player.dart';
import '../player/player_linux.dart';
import '../player/player_windows.dart';
/// Video widget for displaying MPV player output.
/// Video widget for displaying player output.
///
/// This widget displays the video output from an [MpvPlayer] instance
/// This widget displays the video output from a [Player] instance
/// and optionally overlays custom controls.
///
/// Example usage:
/// ```dart
/// final player = MpvPlayer();
/// final player = Player();
///
/// MpvVideo(
/// Video(
/// player: player,
/// fit: BoxFit.contain,
/// controls: (context) => MyCustomControls(),
/// )
/// ```
class MpvVideo extends StatefulWidget {
class Video extends StatefulWidget {
/// The player instance.
final MpvPlayer player;
final Player player;
/// How the video should be inscribed into the widget's box.
final BoxFit fit;
@@ -34,7 +34,7 @@ class MpvVideo extends StatefulWidget {
/// Background color shown behind the video.
final Color backgroundColor;
const MpvVideo({
const Video({
super.key,
required this.player,
this.fit = BoxFit.contain,
@@ -43,10 +43,10 @@ class MpvVideo extends StatefulWidget {
});
@override
State<MpvVideo> createState() => _MpvVideoState();
State<Video> createState() => _VideoState();
}
class _MpvVideoState extends State<MpvVideo> {
class _VideoState extends State<Video> {
Rect? _lastRect;
@override
@@ -123,8 +123,8 @@ class _MpvVideoState extends State<MpvVideo> {
_lastRect = newRect;
// Update the native mpv window position
if (widget.player is MpvPlayerWindows) {
final windowsPlayer = widget.player as MpvPlayerWindows;
if (widget.player is PlayerWindows) {
final windowsPlayer = widget.player as PlayerWindows;
windowsPlayer.setVideoRect(
left: (position.dx * dpr).toInt(),
top: (position.dy * dpr).toInt(),
@@ -161,9 +161,9 @@ class _MpvVideoState extends State<MpvVideo> {
_lastRect = newRect;
// Update the Linux mpv player (triggers redraw)
if (widget.player is MpvPlayerLinux) {
final linuxPlayer = widget.player as MpvPlayerLinux;
// Update the Linux player (triggers redraw)
if (widget.player is PlayerLinux) {
final linuxPlayer = widget.player as PlayerLinux;
linuxPlayer.setVideoRect(
left: (position.dx * dpr).toInt(),
top: (position.dy * dpr).toInt(),
+19 -19
View File
@@ -38,8 +38,8 @@ Future<void> loadIsoTable() async {
class VideoPlayerScreen extends StatefulWidget {
final PlexMetadata metadata;
final MpvAudioTrack? preferredAudioTrack;
final MpvSubtitleTrack? preferredSubtitleTrack;
final AudioTrack? preferredAudioTrack;
final SubtitleTrack? preferredSubtitleTrack;
final double? preferredPlaybackRate;
final int selectedMediaIndex;
@@ -58,7 +58,7 @@ class VideoPlayerScreen extends StatefulWidget {
class VideoPlayerScreenState extends State<VideoPlayerScreen>
with WidgetsBindingObserver {
MpvPlayer? player;
Player? player;
bool _isPlayerInitialized = false;
PlexMetadata? _nextEpisode;
PlexMetadata? _previousEpisode;
@@ -67,13 +67,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
bool _isPhone = false;
List<PlexMediaVersion> _availableVersions = [];
PlexMediaInfo? _currentMediaInfo;
StreamSubscription<MpvLog>? _logSubscription;
StreamSubscription<PlayerLog>? _logSubscription;
StreamSubscription<String>? _errorSubscription;
StreamSubscription<bool>? _playingSubscription;
StreamSubscription<bool>? _completedSubscription;
StreamSubscription<dynamic>? _mediaControlSubscription;
StreamSubscription<bool>? _bufferingSubscription;
StreamSubscription<MpvTracks>? _trackLoadingSubscription;
StreamSubscription<Tracks>? _trackLoadingSubscription;
bool _isReplacingWithVideo =
false; // Flag to skip orientation restoration during video-to-video navigation
@@ -244,7 +244,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
final debugLoggingEnabled = settingsService.getEnableDebugLogging();
// Create player
player = MpvPlayer();
player = Player();
// Configure player properties
await player!.setProperty('target-colorspace-hint', 'yes'); // Enable HDR passthrough
@@ -562,13 +562,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
selectedMediaIndex: widget.selectedMediaIndex,
);
// Open video through MpvPlayer
// Open video through Player
if (result.videoUrl != null) {
// Pass resume position if available
final resumePosition = widget.metadata.viewOffset != null
? Duration(milliseconds: widget.metadata.viewOffset!)
: null;
await player!.open(MpvMedia(result.videoUrl!, start: resumePosition));
await player!.open(Media(result.videoUrl!, start: resumePosition));
}
// Update available versions from the playback data
@@ -709,22 +709,22 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
}
}
void _onPlayerLog(MpvLog log) {
void _onPlayerLog(PlayerLog log) {
// Map MPV log levels to app logger levels
switch (log.level) {
case MpvLogLevel.fatal:
case MpvLogLevel.error:
case PlayerLogLevel.fatal:
case PlayerLogLevel.error:
appLogger.e('[MPV:${log.prefix}] ${log.text}');
break;
case MpvLogLevel.warn:
case PlayerLogLevel.warn:
appLogger.w('[MPV:${log.prefix}] ${log.text}');
break;
case MpvLogLevel.info:
case PlayerLogLevel.info:
appLogger.i('[MPV:${log.prefix}] ${log.text}');
break;
case MpvLogLevel.debug:
case MpvLogLevel.trace:
case MpvLogLevel.verbose:
case PlayerLogLevel.debug:
case PlayerLogLevel.trace:
case PlayerLogLevel.verbose:
appLogger.d('[MPV:${log.prefix}] ${log.text}');
break;
default:
@@ -767,7 +767,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
}
/// Handle audio track changes from the user - save both stream selection and language preference
Future<void> _onAudioTrackChanged(MpvAudioTrack track) async {
Future<void> _onAudioTrackChanged(AudioTrack track) async {
final settings = await SettingsService.getInstance();
// Only save if remember track selections is enabled
@@ -869,7 +869,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
}
/// Handle subtitle track changes from the user - save both stream selection and language preference
Future<void> _onSubtitleTrackChanged(MpvSubtitleTrack track) async {
Future<void> _onSubtitleTrackChanged(SubtitleTrack track) async {
final settings = await SettingsService.getInstance();
// Only save if remember track selections is enabled
@@ -1131,7 +1131,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
});
}
return MpvVideo(
return Video(
player: player!,
fit: _videoFilterManager?.currentBoxFit ?? BoxFit.contain,
controls: (context) => plexVideoControlsBuilder(
+3 -3
View File
@@ -77,14 +77,14 @@ class EpisodeNavigationService {
Future<void> navigateToEpisode({
required BuildContext context,
required PlexMetadata episode,
required MpvPlayer? player,
required Player? player,
bool usePushReplacement = true,
}) async {
if (!context.mounted) return;
// Capture current player state before navigation
MpvAudioTrack? currentAudioTrack;
MpvSubtitleTrack? currentSubtitleTrack;
AudioTrack? currentAudioTrack;
SubtitleTrack? currentSubtitleTrack;
double? currentPlaybackRate;
if (player != null) {
+2 -2
View File
@@ -148,7 +148,7 @@ class KeyboardShortcutsService {
// Handle keyboard input for video player
KeyEventResult handleVideoPlayerKeyEvent(
KeyEvent event,
MpvPlayer player,
Player player,
VoidCallback? onToggleFullscreen,
VoidCallback? onToggleSubtitles,
VoidCallback? onNextAudioTrack,
@@ -241,7 +241,7 @@ class KeyboardShortcutsService {
void _executeAction(
String action,
MpvPlayer player,
Player player,
VoidCallback? onToggleFullscreen,
VoidCallback? onToggleSubtitles,
VoidCallback? onNextAudioTrack,
+1 -1
View File
@@ -15,7 +15,7 @@ import '../utils/app_logger.dart';
class PlaybackProgressTracker {
final PlexClient client;
final PlexMetadata metadata;
final MpvPlayer player;
final Player player;
/// Timer for periodic progress updates
Timer? _progressTimer;
+43 -43
View File
@@ -8,7 +8,7 @@ import '../utils/language_codes.dart';
/// Service for selecting and applying audio and subtitle tracks based on
/// preferences, user profiles, and per-media settings.
class TrackSelectionService {
final MpvPlayer player;
final Player player;
final PlexUserProfile? profileSettings;
final PlexMetadata metadata;
@@ -69,11 +69,11 @@ class TrackSelectionService {
return null;
}
MpvAudioTrack? findBestAudioMatch(
List<MpvAudioTrack> availableTracks,
MpvAudioTrack preferred,
AudioTrack? findBestAudioMatch(
List<AudioTrack> availableTracks,
AudioTrack preferred,
) {
return findBestTrackMatch<MpvAudioTrack>(
return findBestTrackMatch<AudioTrack>(
availableTracks,
preferred,
(t) => t.id,
@@ -82,8 +82,8 @@ class TrackSelectionService {
);
}
MpvAudioTrack? findAudioTrackByProfile(
List<MpvAudioTrack> availableTracks,
AudioTrack? findAudioTrackByProfile(
List<AudioTrack> availableTracks,
PlexUserProfile profile,
) {
appLogger.d('Audio track selection using user profile');
@@ -123,7 +123,7 @@ class TrackSelectionService {
'Checking language variations for "$preferredLanguage": ${languageVariations.join(", ")}',
);
final match = _findTrackByLanguageVariations<MpvAudioTrack>(
final match = _findTrackByLanguageVariations<AudioTrack>(
availableTracks,
preferredLanguage,
languageVariations,
@@ -140,16 +140,16 @@ class TrackSelectionService {
return null;
}
MpvSubtitleTrack? findBestSubtitleMatch(
List<MpvSubtitleTrack> availableTracks,
MpvSubtitleTrack preferred,
SubtitleTrack? findBestSubtitleMatch(
List<SubtitleTrack> availableTracks,
SubtitleTrack preferred,
) {
// Handle special "no subtitles" case
if (preferred.id == 'no') {
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
}
return findBestTrackMatch<MpvSubtitleTrack>(
return findBestTrackMatch<SubtitleTrack>(
availableTracks,
preferred,
(t) => t.id,
@@ -158,10 +158,10 @@ class TrackSelectionService {
);
}
MpvSubtitleTrack? findSubtitleTrackByProfile(
List<MpvSubtitleTrack> availableTracks,
SubtitleTrack? findSubtitleTrackByProfile(
List<SubtitleTrack> availableTracks,
PlexUserProfile profile, {
MpvAudioTrack? selectedAudioTrack,
AudioTrack? selectedAudioTrack,
}) {
appLogger.d('Subtitle track selection using user profile');
appLogger.d(
@@ -178,7 +178,7 @@ class TrackSelectionService {
appLogger.d(
'Profile specifies manual mode (autoSelectSubtitle=0) - Subtitles OFF',
);
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
}
// Mode 1: Shown with foreign audio
@@ -201,7 +201,7 @@ class TrackSelectionService {
// If audio matches preferred language, no subtitles needed
if (audioLang != null && languageVariations.contains(audioLang)) {
appLogger.d('Audio matches preferred language - Subtitles OFF');
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
}
appLogger.d('Foreign audio detected - enabling subtitles');
}
@@ -258,7 +258,7 @@ class TrackSelectionService {
'Checking language variations for "$preferredLanguage": ${languageVariations.join(", ")}',
);
final match = _findTrackByLanguageVariations<MpvSubtitleTrack>(
final match = _findTrackByLanguageVariations<SubtitleTrack>(
candidateTracks,
preferredLanguage,
languageVariations,
@@ -282,8 +282,8 @@ class TrackSelectionService {
/// - 1: Prefer SDH subtitles
/// - 2: Only show SDH subtitles
/// - 3: Only show non-SDH subtitles
List<MpvSubtitleTrack> filterSubtitlesBySDH(
List<MpvSubtitleTrack> tracks,
List<SubtitleTrack> filterSubtitlesBySDH(
List<SubtitleTrack> tracks,
int preference,
) {
if (preference == 0 || preference == 1) {
@@ -319,8 +319,8 @@ class TrackSelectionService {
/// - 1: Prefer forced subtitles
/// - 2: Only show forced subtitles
/// - 3: Only show non-forced subtitles
List<MpvSubtitleTrack> filterSubtitlesByForced(
List<MpvSubtitleTrack> tracks,
List<SubtitleTrack> filterSubtitlesByForced(
List<SubtitleTrack> tracks,
int preference,
) {
if (preference == 0 || preference == 1) {
@@ -354,7 +354,7 @@ class TrackSelectionService {
/// Checks if a subtitle track is SDH (Subtitles for Deaf or Hard-of-Hearing)
///
/// Since mpv may not expose this directly, we infer from the title
bool isSDH(MpvSubtitleTrack track) {
bool isSDH(SubtitleTrack track) {
final title = track.title?.toLowerCase() ?? '';
// Look for common SDH indicators
@@ -365,7 +365,7 @@ class TrackSelectionService {
}
/// Checks if a subtitle track is forced
bool isForced(MpvSubtitleTrack track) {
bool isForced(SubtitleTrack track) {
final title = track.title?.toLowerCase() ?? '';
return title.contains('forced');
}
@@ -423,8 +423,8 @@ class TrackSelectionService {
/// Log available tracks for debugging
void logAvailableTracks(
List<MpvAudioTrack> audioTracks,
List<MpvSubtitleTrack> subtitleTracks,
List<AudioTrack> audioTracks,
List<SubtitleTrack> subtitleTracks,
) {
appLogger.d('Available audio tracks: ${audioTracks.length}');
for (var track in audioTracks) {
@@ -445,13 +445,13 @@ class TrackSelectionService {
/// Priority 2: Per-media language preference
/// Priority 3: User profile preferences
/// Priority 4: Default or first track
MpvAudioTrack? selectAudioTrack(
List<MpvAudioTrack> availableTracks,
MpvAudioTrack? preferredAudioTrack,
AudioTrack? selectAudioTrack(
List<AudioTrack> availableTracks,
AudioTrack? preferredAudioTrack,
) {
if (availableTracks.isEmpty) return null;
MpvAudioTrack? trackToSelect;
AudioTrack? trackToSelect;
// Priority 1: Try to match preferred track from navigation
if (preferredAudioTrack != null) {
@@ -522,19 +522,19 @@ class TrackSelectionService {
/// Priority 3: User profile preferences
/// Priority 4: Default track
/// Priority 5: Off
MpvSubtitleTrack selectSubtitleTrack(
List<MpvSubtitleTrack> availableTracks,
MpvSubtitleTrack? preferredSubtitleTrack,
MpvAudioTrack? selectedAudioTrack,
SubtitleTrack selectSubtitleTrack(
List<SubtitleTrack> availableTracks,
SubtitleTrack? preferredSubtitleTrack,
AudioTrack? selectedAudioTrack,
) {
MpvSubtitleTrack? subtitleToSelect;
SubtitleTrack? subtitleToSelect;
// Priority 1: Try preferred track from navigation (always wins)
if (preferredSubtitleTrack != null) {
appLogger.d('Priority 1: Checking preferred track from navigation');
if (preferredSubtitleTrack.id == 'no') {
appLogger.d(' Preferred: OFF');
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
} else if (availableTracks.isNotEmpty) {
appLogger.d(
' Preferred: ${preferredSubtitleTrack.title ?? "Track ${preferredSubtitleTrack.id}"} (${preferredSubtitleTrack.language ?? "unknown"})',
@@ -566,7 +566,7 @@ class TrackSelectionService {
if (metadata.subtitleLanguage == 'none' ||
metadata.subtitleLanguage!.isEmpty) {
appLogger.d(' Per-media preference: Subtitles OFF');
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
} else if (availableTracks.isNotEmpty) {
final matchedTrack = availableTracks.firstWhere(
(track) => languageMatches(track.language, metadata.subtitleLanguage),
@@ -615,16 +615,16 @@ class TrackSelectionService {
// Priority 5: If still no subtitle selected, turn off
appLogger.d('Priority 5: No subtitle selected - Subtitles OFF');
return MpvSubtitleTrack.off;
return SubtitleTrack.off;
}
/// Select and apply audio and subtitle tracks based on preferences
Future<void> selectAndApplyTracks({
MpvAudioTrack? preferredAudioTrack,
MpvSubtitleTrack? preferredSubtitleTrack,
AudioTrack? preferredAudioTrack,
SubtitleTrack? preferredSubtitleTrack,
double? preferredPlaybackRate,
Function(MpvAudioTrack)? onAudioTrackChanged,
Function(MpvSubtitleTrack)? onSubtitleTrackChanged,
Function(AudioTrack)? onAudioTrackChanged,
Function(SubtitleTrack)? onSubtitleTrackChanged,
}) async {
// Wait for tracks to be loaded
int attempts = 0;
+1 -1
View File
@@ -14,7 +14,7 @@ import '../utils/app_logger.dart';
/// - Subtitle positioning adjustments based on crop parameters
/// - Debounced video filter updates on resize events
class VideoFilterManager {
final MpvPlayer player;
final Player player;
final List<PlexMediaVersion> availableVersions;
final int selectedMediaIndex;
+1 -1
View File
@@ -2,7 +2,7 @@ import '../mpv/mpv.dart';
/// Seeks by the given offset (can be positive or negative) while clamping
/// the result between 0 and the video duration
void seekWithClamping(MpvPlayer player, Duration offset) {
void seekWithClamping(Player player, Duration offset) {
final currentPosition = player.state.position;
final duration = player.state.duration;
final newPosition = currentPosition + offset;
+2 -2
View File
@@ -27,8 +27,8 @@ import '../services/settings_service.dart';
Future<bool?> navigateToVideoPlayer(
BuildContext context, {
required PlexMetadata metadata,
MpvAudioTrack? preferredAudioTrack,
MpvSubtitleTrack? preferredSubtitleTrack,
AudioTrack? preferredAudioTrack,
SubtitleTrack? preferredSubtitleTrack,
double? preferredPlaybackRate,
int? selectedMediaIndex,
bool usePushReplacement = false,
@@ -14,7 +14,7 @@ import 'widgets/timeline_slider.dart';
/// Desktop-specific video controls layout with top bar and bottom controls
class DesktopVideoControls extends StatelessWidget {
final MpvPlayer player;
final Player player;
final PlexMetadata metadata;
final VoidCallback? onNext;
final VoidCallback? onPrevious;
@@ -18,7 +18,7 @@ import 'widgets/timeline_slider.dart';
/// - Center: Large playback controls (seek backward, play/pause, seek forward)
/// - Bottom bar: Timeline slider with chapter markers and timestamps
class MobileVideoControls extends StatelessWidget {
final MpvPlayer player;
final Player player;
final PlexMetadata metadata;
final List<PlexChapter> chapters;
final bool chaptersLoaded;
@@ -8,12 +8,12 @@ import 'track_selection_sheet.dart';
class AudioTrackSheet {
static void show(
BuildContext context,
MpvPlayer player, {
Function(MpvAudioTrack)? onTrackChanged,
Player player, {
Function(AudioTrack)? onTrackChanged,
VoidCallback? onOpen,
VoidCallback? onClose,
}) {
TrackSelectionSheet.show<MpvAudioTrack>(
TrackSelectionSheet.show<AudioTrack>(
context: context,
player: player,
title: t.videoControls.audioLabel,
@@ -9,7 +9,7 @@ import 'base_video_control_sheet.dart';
/// Bottom sheet for selecting chapters
class ChapterSheet extends StatelessWidget {
final MpvPlayer player;
final Player player;
final List<PlexChapter> chapters;
final bool chaptersLoaded;
final String? serverId; // Server ID for the metadata these chapters belong to
@@ -24,7 +24,7 @@ class ChapterSheet extends StatelessWidget {
static void show(
BuildContext context,
MpvPlayer player,
Player player,
List<PlexChapter> chapters,
bool chaptersLoaded, {
String? serverId,
@@ -8,12 +8,12 @@ import 'track_selection_sheet.dart';
class SubtitleTrackSheet {
static void show(
BuildContext context,
MpvPlayer player, {
Function(MpvSubtitleTrack)? onTrackChanged,
Player player, {
Function(SubtitleTrack)? onTrackChanged,
VoidCallback? onOpen,
VoidCallback? onClose,
}) {
TrackSelectionSheet.show<MpvSubtitleTrack>(
TrackSelectionSheet.show<SubtitleTrack>(
context: context,
player: player,
title: t.videoControls.subtitlesLabel,
@@ -47,7 +47,7 @@ class SubtitleTrackSheet {
setTrack: (track) => player.selectSubtitleTrack(track),
onTrackChanged: onTrackChanged,
showOffOption: true,
createOffTrack: () => MpvSubtitleTrack.off,
createOffTrack: () => SubtitleTrack.off,
isOffTrack: (track) => track.id == 'no',
onOpen: onOpen,
onClose: onClose,
@@ -5,13 +5,13 @@ import 'base_video_control_sheet.dart';
/// Generic track selection sheet for audio and subtitle tracks
///
/// Type parameter [T] should be either [MpvAudioTrack] or [MpvSubtitleTrack]
/// Type parameter [T] should be either [AudioTrack] or [SubtitleTrack]
class TrackSelectionSheet<T> extends StatelessWidget {
final MpvPlayer player;
final Player player;
final String title;
final IconData icon;
final List<T> Function(MpvTracks?) extractTracks;
final T? Function(MpvTrack) getCurrentTrack;
final List<T> Function(Tracks?) extractTracks;
final T? Function(TrackSelection) getCurrentTrack;
final String Function(T track, int index) buildLabel;
final void Function(T track) setTrack;
final Function(T)? onTrackChanged;
@@ -36,11 +36,11 @@ class TrackSelectionSheet<T> extends StatelessWidget {
static void show<T>({
required BuildContext context,
required MpvPlayer player,
required Player player,
required String title,
required IconData icon,
required List<T> Function(MpvTracks?) extractTracks,
required T? Function(MpvTrack) getCurrentTrack,
required List<T> Function(Tracks?) extractTracks,
required T? Function(TrackSelection) getCurrentTrack,
required String Function(T track, int index) buildLabel,
required void Function(T track) setTrack,
Function(T)? onTrackChanged,
@@ -71,9 +71,9 @@ class TrackSelectionSheet<T> extends StatelessWidget {
}
String _getEmptyMessage() {
if (T == MpvSubtitleTrack) {
if (T == SubtitleTrack) {
return 'No subtitles available';
} else if (T == MpvAudioTrack) {
} else if (T == AudioTrack) {
return 'No audio tracks available';
}
return 'No tracks available';
@@ -81,16 +81,16 @@ class TrackSelectionSheet<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<MpvTracks>(
return StreamBuilder<Tracks>(
stream: player.streams.tracks,
initialData: player.state.tracks,
builder: (context, snapshot) {
final tracks = snapshot.data;
final availableTracks = extractTracks(tracks).where((track) {
// Filter out 'auto' and 'no' tracks from the list
if (track is MpvAudioTrack) {
if (track is AudioTrack) {
return track.id != 'auto' && track.id != 'no';
} else if (track is MpvSubtitleTrack) {
} else if (track is SubtitleTrack) {
return track.id != 'auto' && track.id != 'no';
}
return true;
@@ -106,7 +106,7 @@ class TrackSelectionSheet<T> extends StatelessWidget {
style: const TextStyle(color: Colors.white70),
),
)
: StreamBuilder<MpvTrack>(
: StreamBuilder<TrackSelection>(
stream: player.streams.track,
initialData: player.state.track,
builder: (context, selectedSnapshot) {
@@ -155,9 +155,9 @@ class TrackSelectionSheet<T> extends StatelessWidget {
// Check if this track is selected
String trackId;
if (track is MpvAudioTrack) {
if (track is AudioTrack) {
trackId = track.id;
} else if (track is MpvSubtitleTrack) {
} else if (track is SubtitleTrack) {
trackId = track.id;
} else {
trackId = '';
@@ -166,9 +166,9 @@ class TrackSelectionSheet<T> extends StatelessWidget {
String selectedId;
if (selectedTrack == null) {
selectedId = '';
} else if (selectedTrack is MpvAudioTrack) {
} else if (selectedTrack is AudioTrack) {
selectedId = selectedTrack.id;
} else if (selectedTrack is MpvSubtitleTrack) {
} else if (selectedTrack is SubtitleTrack) {
selectedId = selectedTrack.id;
} else {
selectedId = '';
@@ -59,7 +59,7 @@ class _SettingsMenuItem extends StatelessWidget {
/// Unified settings sheet for playback adjustments with in-sheet navigation
class VideoSettingsSheet extends StatefulWidget {
final MpvPlayer player;
final Player player;
final int audioSyncOffset;
final int subtitleSyncOffset;
@@ -72,7 +72,7 @@ class VideoSettingsSheet extends StatefulWidget {
static Future<void> show(
BuildContext context,
MpvPlayer player,
Player player,
int audioSyncOffset,
int subtitleSyncOffset, {
VoidCallback? onOpen,
@@ -254,7 +254,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
// Audio Output Device (Desktop only)
if (isDesktop)
StreamBuilder<MpvAudioDevice>(
StreamBuilder<AudioDevice>(
stream: widget.player.streams.audioDevice,
initialData: widget.player.state.audioDevice,
builder: (context, snapshot) {
@@ -358,13 +358,13 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
}
Widget _buildAudioDeviceView() {
return StreamBuilder<List<MpvAudioDevice>>(
return StreamBuilder<List<AudioDevice>>(
stream: widget.player.streams.audioDevices,
initialData: widget.player.state.audioDevices,
builder: (context, snapshot) {
final devices = snapshot.data ?? [];
return StreamBuilder<MpvAudioDevice>(
return StreamBuilder<AudioDevice>(
stream: widget.player.streams.audioDevice,
initialData: widget.player.state.audioDevice,
builder: (context, selectedSnapshot) {
@@ -28,7 +28,7 @@ import 'desktop_video_controls.dart';
/// Custom video controls builder for Plex with chapter, audio, and subtitle support
Widget plexVideoControlsBuilder(
MpvPlayer player,
Player player,
PlexMetadata metadata, {
VoidCallback? onNext,
VoidCallback? onPrevious,
@@ -36,8 +36,8 @@ Widget plexVideoControlsBuilder(
int? selectedMediaIndex,
int boxFitMode = 0,
VoidCallback? onCycleBoxFitMode,
Function(MpvAudioTrack)? onAudioTrackChanged,
Function(MpvSubtitleTrack)? onSubtitleTrackChanged,
Function(AudioTrack)? onAudioTrackChanged,
Function(SubtitleTrack)? onSubtitleTrackChanged,
}) {
return PlexVideoControls(
player: player,
@@ -54,7 +54,7 @@ Widget plexVideoControlsBuilder(
}
class PlexVideoControls extends StatefulWidget {
final MpvPlayer player;
final Player player;
final PlexMetadata metadata;
final VoidCallback? onNext;
final VoidCallback? onPrevious;
@@ -62,8 +62,8 @@ class PlexVideoControls extends StatefulWidget {
final int selectedMediaIndex;
final int boxFitMode;
final VoidCallback? onCycleBoxFitMode;
final Function(MpvAudioTrack)? onAudioTrackChanged;
final Function(MpvSubtitleTrack)? onSubtitleTrackChanged;
final Function(AudioTrack)? onAudioTrackChanged;
final Function(SubtitleTrack)? onSubtitleTrackChanged;
const PlexVideoControls({
super.key,
@@ -7,7 +7,7 @@ import 'sleep_timer_duration_list.dart';
/// Shared UI for sleep timer selection and active status.
class SleepTimerContent extends StatelessWidget {
final MpvPlayer player;
final Player player;
final SleepTimerService sleepTimer;
final int? defaultDuration;
final VoidCallback? onCancel;
@@ -7,7 +7,7 @@ import '../../../i18n/strings.g.dart';
/// Widget displaying list of sleep timer durations for selection
class SleepTimerDurationList extends StatelessWidget {
final MpvPlayer player;
final Player player;
final SleepTimerService sleepTimer;
final int? defaultDuration;
@@ -6,7 +6,7 @@ import '../../../utils/duration_formatter.dart';
/// Reusable widget for adjusting sync offsets (audio or subtitle)
class SyncOffsetControl extends StatefulWidget {
final MpvPlayer player;
final Player player;
final String propertyName; // 'audio-delay' or 'sub-delay'
final int initialOffset;
final String labelText; // 'Audio' or 'Subtitles'
@@ -17,7 +17,7 @@ import '../video_control_button.dart';
/// Row of track and chapter control buttons for the video player
class TrackChapterControls extends StatelessWidget {
final MpvPlayer player;
final Player player;
final List<PlexChapter> chapters;
final bool chaptersLoaded;
final List<PlexMediaVersion> availableVersions;
@@ -31,8 +31,8 @@ class TrackChapterControls extends StatelessWidget {
final VoidCallback? onToggleRotationLock;
final VoidCallback? onToggleFullscreen;
final Function(int)? onSwitchVersion;
final Function(MpvAudioTrack)? onAudioTrackChanged;
final Function(MpvSubtitleTrack)? onSubtitleTrackChanged;
final Function(AudioTrack)? onAudioTrackChanged;
final Function(SubtitleTrack)? onSubtitleTrackChanged;
final VoidCallback? onLoadSeekTimes;
final VoidCallback? onCancelAutoHide;
final VoidCallback? onStartAutoHide;
@@ -64,7 +64,7 @@ class TrackChapterControls extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<MpvTracks>(
return StreamBuilder<Tracks>(
stream: player.streams.tracks,
initialData: player.state.tracks,
builder: (context, snapshot) {
@@ -189,7 +189,7 @@ class TrackChapterControls extends StatelessWidget {
);
}
bool _hasMultipleAudioTracks(MpvTracks? tracks) {
bool _hasMultipleAudioTracks(Tracks? tracks) {
if (tracks == null) return false;
final audioTracks = tracks.audio
.where((track) => track.id != 'auto' && track.id != 'no')
@@ -197,7 +197,7 @@ class TrackChapterControls extends StatelessWidget {
return audioTracks.length > 1;
}
bool _hasSubtitles(MpvTracks? tracks) {
bool _hasSubtitles(Tracks? tracks) {
if (tracks == null) return false;
final subtitles = tracks.subtitle
.where((track) => track.id != 'auto' && track.id != 'no')
@@ -6,10 +6,10 @@ import '../../../i18n/strings.g.dart';
/// A volume control widget that displays a mute/unmute button and volume slider.
///
/// This widget integrates with [MpvPlayer] to control volume and persists
/// This widget integrates with [Player] to control volume and persists
/// the volume setting using [SettingsService].
class VolumeControl extends StatelessWidget {
final MpvPlayer player;
final Player player;
const VolumeControl({super.key, required this.player});