refactor: fix naming
This commit is contained in:
+18
-18
@@ -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;
|
||||
|
||||
@@ -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 {}
|
||||
@@ -1,5 +0,0 @@
|
||||
import 'mpv_player_native.dart';
|
||||
|
||||
/// iOS implementation of MpvPlayer.
|
||||
/// Inherits all functionality from MpvPlayerNative.
|
||||
class MpvPlayerIOS extends MpvPlayerNative {}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import 'player_native.dart';
|
||||
|
||||
/// Android implementation of Player.
|
||||
/// Inherits all functionality from PlayerNative.
|
||||
class PlayerAndroid extends PlayerNative {}
|
||||
@@ -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
|
||||
@@ -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)';
|
||||
}
|
||||
+13
-13
@@ -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(),
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user