refactor: reduce requests made for playback
This commit is contained in:
@@ -10,6 +10,7 @@ import '../models/plex_library.dart';
|
||||
import '../models/plex_media_info.dart';
|
||||
import '../models/plex_media_version.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_video_playback_data.dart';
|
||||
import '../models/plex_sort.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
|
||||
@@ -639,6 +640,124 @@ class PlexClient {
|
||||
return [];
|
||||
}
|
||||
|
||||
/// Get consolidated video playback data (URL, media info, and versions) in a single API call
|
||||
/// This method combines the functionality of getVideoUrl(), getMediaInfo(), and getMediaVersions()
|
||||
/// to reduce redundant API calls during video playback initialization.
|
||||
Future<PlexVideoPlaybackData> getVideoPlaybackData(
|
||||
String ratingKey, {
|
||||
int mediaIndex = 0,
|
||||
}) async {
|
||||
final response = await _dio.get('/library/metadata/$ratingKey');
|
||||
final metadataJson = _getFirstMetadataJson(response);
|
||||
|
||||
String? videoUrl;
|
||||
PlexMediaInfo? mediaInfo;
|
||||
List<PlexMediaVersion> availableVersions = [];
|
||||
|
||||
if (metadataJson != null &&
|
||||
metadataJson['Media'] != null &&
|
||||
(metadataJson['Media'] as List).isNotEmpty) {
|
||||
final mediaList = metadataJson['Media'] as List;
|
||||
|
||||
// Parse available media versions first
|
||||
availableVersions = mediaList
|
||||
.map(
|
||||
(media) => PlexMediaVersion.fromJson(media as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
// Ensure the requested index is valid
|
||||
if (mediaIndex < 0 || mediaIndex >= mediaList.length) {
|
||||
mediaIndex = 0;
|
||||
}
|
||||
|
||||
final media = mediaList[mediaIndex];
|
||||
if (media['Part'] != null && (media['Part'] as List).isNotEmpty) {
|
||||
final part = media['Part'][0];
|
||||
final partKey = part['key'] as String?;
|
||||
|
||||
if (partKey != null) {
|
||||
// Get video URL
|
||||
videoUrl = '${config.baseUrl}$partKey?X-Plex-Token=${config.token}';
|
||||
|
||||
// Parse streams (audio and subtitle tracks) for media info
|
||||
final streams = part['Stream'] as List<dynamic>? ?? [];
|
||||
final audioTracks = <PlexAudioTrack>[];
|
||||
final subtitleTracks = <PlexSubtitleTrack>[];
|
||||
|
||||
for (var stream in streams) {
|
||||
final streamType = stream['streamType'] as int?;
|
||||
|
||||
if (streamType == 2) {
|
||||
// Audio track
|
||||
audioTracks.add(
|
||||
PlexAudioTrack(
|
||||
id: stream['id'] as int,
|
||||
index: stream['index'] as int?,
|
||||
codec: stream['codec'] as String?,
|
||||
language: stream['language'] as String?,
|
||||
languageCode: stream['languageCode'] as String?,
|
||||
title: stream['title'] as String?,
|
||||
displayTitle: stream['displayTitle'] as String?,
|
||||
channels: stream['channels'] as int?,
|
||||
selected: stream['selected'] == 1,
|
||||
),
|
||||
);
|
||||
} else if (streamType == 3) {
|
||||
// Subtitle track
|
||||
subtitleTracks.add(
|
||||
PlexSubtitleTrack(
|
||||
id: stream['id'] as int,
|
||||
index: stream['index'] as int?,
|
||||
codec: stream['codec'] as String?,
|
||||
language: stream['language'] as String?,
|
||||
languageCode: stream['languageCode'] as String?,
|
||||
title: stream['title'] as String?,
|
||||
displayTitle: stream['displayTitle'] as String?,
|
||||
selected: stream['selected'] == 1,
|
||||
forced: stream['forced'] == 1,
|
||||
key: stream['key'] as String?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse chapters
|
||||
final chapters = <PlexChapter>[];
|
||||
if (metadataJson['Chapter'] != null) {
|
||||
final chapterList = metadataJson['Chapter'] as List<dynamic>;
|
||||
for (var chapter in chapterList) {
|
||||
chapters.add(
|
||||
PlexChapter(
|
||||
id: chapter['id'] as int,
|
||||
index: chapter['index'] as int?,
|
||||
startTimeOffset: chapter['startTimeOffset'] as int?,
|
||||
endTimeOffset: chapter['endTimeOffset'] as int?,
|
||||
title: chapter['title'] as String?,
|
||||
thumb: chapter['thumb'] as String?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create media info
|
||||
mediaInfo = PlexMediaInfo(
|
||||
videoUrl: videoUrl,
|
||||
audioTracks: audioTracks,
|
||||
subtitleTracks: subtitleTracks,
|
||||
chapters: chapters,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PlexVideoPlaybackData(
|
||||
videoUrl: videoUrl,
|
||||
mediaInfo: mediaInfo,
|
||||
availableVersions: availableVersions,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get file information for a media item
|
||||
Future<PlexFileInfo?> getFileInfo(String ratingKey) async {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'plex_media_info.dart';
|
||||
import 'plex_media_version.dart';
|
||||
|
||||
/// Consolidated data model containing all information needed for video playback.
|
||||
/// This model combines data from multiple Plex API endpoints to reduce redundant requests.
|
||||
class PlexVideoPlaybackData {
|
||||
/// Direct video URL for playback
|
||||
final String? videoUrl;
|
||||
|
||||
/// Media information including audio/subtitle tracks and chapters
|
||||
final PlexMediaInfo? mediaInfo;
|
||||
|
||||
/// Available media versions/qualities for this content
|
||||
final List<PlexMediaVersion> availableVersions;
|
||||
|
||||
PlexVideoPlaybackData({
|
||||
required this.videoUrl,
|
||||
required this.mediaInfo,
|
||||
required this.availableVersions,
|
||||
});
|
||||
|
||||
/// Returns true if this playback data has a valid video URL
|
||||
bool get hasValidVideoUrl => videoUrl != null && videoUrl!.isNotEmpty;
|
||||
|
||||
/// Returns true if media info is available
|
||||
bool get hasMediaInfo => mediaInfo != null;
|
||||
|
||||
/// Returns true if there are multiple media versions available
|
||||
bool get hasMultipleVersions => availableVersions.length > 1;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
|
||||
import '../models/plex_media_version.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../models/plex_video_playback_data.dart';
|
||||
import '../providers/playback_state_provider.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../providers/settings_provider.dart';
|
||||
@@ -181,9 +182,6 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
// Get the video URL and start playback
|
||||
_startPlayback();
|
||||
|
||||
// Load available media versions
|
||||
_loadMediaVersions();
|
||||
|
||||
// Set fullscreen mode and orientation based on rotation lock setting
|
||||
if (mounted) {
|
||||
try {
|
||||
@@ -304,18 +302,22 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
throw Exception('No client available');
|
||||
}
|
||||
|
||||
// Get the direct file URL from the server using the selected media index
|
||||
final videoUrl = await client.getVideoUrl(
|
||||
// Get consolidated playback data (URL, media info, and versions) in a single API call
|
||||
final playbackData = await client.getVideoPlaybackData(
|
||||
widget.metadata.ratingKey,
|
||||
mediaIndex: widget.selectedMediaIndex,
|
||||
);
|
||||
|
||||
if (videoUrl != null) {
|
||||
// Fetch media info to check for external subtitle tracks
|
||||
final mediaInfo = await client.getMediaInfo(
|
||||
widget.metadata.ratingKey,
|
||||
mediaIndex: widget.selectedMediaIndex,
|
||||
);
|
||||
if (playbackData.hasValidVideoUrl) {
|
||||
final videoUrl = playbackData.videoUrl!;
|
||||
final mediaInfo = playbackData.mediaInfo;
|
||||
|
||||
// Update available versions from the playback data
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_availableVersions = playbackData.availableVersions;
|
||||
});
|
||||
}
|
||||
|
||||
// Build list of external subtitle tracks for media_kit
|
||||
final externalSubtitles = <SubtitleTrack>[];
|
||||
@@ -434,24 +436,6 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load available media versions for this item
|
||||
Future<void> _loadMediaVersions() async {
|
||||
try {
|
||||
final clientProvider = context.plexClient;
|
||||
final client = clientProvider.client;
|
||||
if (client == null) return;
|
||||
|
||||
final versions = await client.getMediaVersions(widget.metadata.ratingKey);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_availableVersions = versions;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.e('Error loading media versions: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle through BoxFit modes: contain → cover → fill → contain (for button)
|
||||
void _cycleBoxFitMode() {
|
||||
setState(() {
|
||||
|
||||
Reference in New Issue
Block a user