Files
plezy/lib/widgets/video_controls/sheets/track_selection_sheet.dart
T
2025-12-02 15:46:19 +01:00

201 lines
7.2 KiB
Dart

import 'package:flutter/material.dart';
import '../../../mpv/mpv.dart';
import 'base_video_control_sheet.dart';
/// Generic track selection sheet for audio and subtitle tracks
///
/// Type parameter [T] should be either [MpvAudioTrack] or [MpvSubtitleTrack]
class TrackSelectionSheet<T> extends StatelessWidget {
final MpvPlayer player;
final String title;
final IconData icon;
final List<T> Function(MpvTracks?) extractTracks;
final T? Function(MpvTrack) getCurrentTrack;
final String Function(T track, int index) buildLabel;
final void Function(T track) setTrack;
final Function(T)? onTrackChanged;
final bool showOffOption;
final T Function()? createOffTrack;
final bool Function(T track)? isOffTrack;
const TrackSelectionSheet({
super.key,
required this.player,
required this.title,
required this.icon,
required this.extractTracks,
required this.getCurrentTrack,
required this.buildLabel,
required this.setTrack,
this.onTrackChanged,
this.showOffOption = false,
this.createOffTrack,
this.isOffTrack,
});
static void show<T>({
required BuildContext context,
required MpvPlayer player,
required String title,
required IconData icon,
required List<T> Function(MpvTracks?) extractTracks,
required T? Function(MpvTrack) getCurrentTrack,
required String Function(T track, int index) buildLabel,
required void Function(T track) setTrack,
Function(T)? onTrackChanged,
bool showOffOption = false,
T Function()? createOffTrack,
bool Function(T track)? isOffTrack,
}) {
BaseVideoControlSheet.showSheet(
context: context,
builder: (context) => TrackSelectionSheet<T>(
player: player,
title: title,
icon: icon,
extractTracks: extractTracks,
getCurrentTrack: getCurrentTrack,
buildLabel: buildLabel,
setTrack: setTrack,
onTrackChanged: onTrackChanged,
showOffOption: showOffOption,
createOffTrack: createOffTrack,
isOffTrack: isOffTrack,
),
);
}
String _getEmptyMessage() {
if (T == MpvSubtitleTrack) {
return 'No subtitles available';
} else if (T == MpvAudioTrack) {
return 'No audio tracks available';
}
return 'No tracks available';
}
@override
Widget build(BuildContext context) {
return StreamBuilder<MpvTracks>(
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) {
return track.id != 'auto' && track.id != 'no';
} else if (track is MpvSubtitleTrack) {
return track.id != 'auto' && track.id != 'no';
}
return true;
}).toList();
return BaseVideoControlSheet(
title: title,
icon: icon,
child: availableTracks.isEmpty
? Center(
child: Text(
_getEmptyMessage(),
style: const TextStyle(color: Colors.white70),
),
)
: StreamBuilder<MpvTrack>(
stream: player.streams.track,
initialData: player.state.track,
builder: (context, selectedSnapshot) {
final currentTrack =
selectedSnapshot.data ?? player.state.track;
final selectedTrack = getCurrentTrack(currentTrack);
// Determine if "Off" is selected (null or explicit off)
final isOffSelected = selectedTrack == null ||
(isOffTrack?.call(selectedTrack) ?? false);
final itemCount =
availableTracks.length + (showOffOption ? 1 : 0);
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
// First item is "Off" if enabled
if (showOffOption && index == 0) {
return ListTile(
title: Text(
'Off',
style: TextStyle(
color: isOffSelected
? Colors.blue
: Colors.white,
),
),
trailing: isOffSelected
? const Icon(Icons.check, color: Colors.blue)
: null,
onTap: () {
if (createOffTrack != null) {
final offTrack = createOffTrack!();
setTrack(offTrack);
onTrackChanged?.call(offTrack);
}
Navigator.pop(context);
},
);
}
// Subsequent items are tracks
final trackIndex = showOffOption ? index - 1 : index;
final track = availableTracks[trackIndex];
// Check if this track is selected
String trackId;
if (track is MpvAudioTrack) {
trackId = track.id;
} else if (track is MpvSubtitleTrack) {
trackId = track.id;
} else {
trackId = '';
}
String selectedId;
if (selectedTrack == null) {
selectedId = '';
} else if (selectedTrack is MpvAudioTrack) {
selectedId = selectedTrack.id;
} else if (selectedTrack is MpvSubtitleTrack) {
selectedId = selectedTrack.id;
} else {
selectedId = '';
}
final isSelected = trackId == selectedId;
final label = buildLabel(track, trackIndex);
return ListTile(
title: Text(
label,
style: TextStyle(
color: isSelected ? Colors.blue : Colors.white,
),
),
trailing: isSelected
? const Icon(Icons.check, color: Colors.blue)
: null,
onTap: () {
setTrack(track);
onTrackChanged?.call(track);
Navigator.pop(context);
},
);
},
);
},
),
);
},
);
}
}