Initial commit
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
|
||||
|
||||
part of 'player_page.dart';
|
||||
|
||||
extension _PlayerPageSources on _PlayerPageState {
|
||||
PlaybackRequest _itemToRequest(PlaylistItem item) {
|
||||
return PlaybackRequest(
|
||||
itemId: item.itemId,
|
||||
mediaSourceId: item.mediaSourceId,
|
||||
startPositionTicks: item.startPositionTicks,
|
||||
preferredAudioStreamIndex: item.preferredAudioStreamIndex,
|
||||
preferredSubtitleStreamIndex: item.preferredSubtitleStreamIndex,
|
||||
subtitleSettings: _subtitleSettings,
|
||||
playerSettings: _lastRequest?.playerSettings ?? _initialPlayerSettings,
|
||||
danmakuSettings: _lastRequest?.danmakuSettings ?? _initialDanmakuSettings,
|
||||
audioSettings: _lastRequest?.audioSettings ?? _initialAudioSettings,
|
||||
playFromStart: item.playFromStart,
|
||||
targetServer: item.serverIdentity,
|
||||
preloadedItem: item.preloadedItem,
|
||||
directStreamUrl: widget.directStream?.url,
|
||||
directStreamHeaders:
|
||||
widget.directStream?.headers ?? const <String, String>{},
|
||||
);
|
||||
}
|
||||
|
||||
void _onCurrentItemChanged() {
|
||||
final pl = _playlist;
|
||||
if (pl == null) return;
|
||||
final item = pl.current.value;
|
||||
if (item.itemId.isEmpty) return;
|
||||
|
||||
_relink.reset();
|
||||
unawaited(_switchTo(_itemToRequest(item)));
|
||||
}
|
||||
|
||||
Future<void> _onEpisodePicked(EmbyRawItem episode) async {
|
||||
final pl = _playlist;
|
||||
if (pl == null) return;
|
||||
if (episode.Id == _session?.context.itemId) return;
|
||||
if (pl is SeriesEpisodePlaylist && episode.SeasonId != null) {
|
||||
await pl.goToInSeason(seasonId: episode.SeasonId!, episodeId: episode.Id);
|
||||
} else {
|
||||
await pl.goToItemId(episode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _switchVersion(String sourceId) async {
|
||||
if (sourceId == _session?.context.mediaSourceId) return;
|
||||
final base = _lastRequest;
|
||||
if (base == null) return;
|
||||
_relink.reset();
|
||||
final pos = _session?.engine.state.position;
|
||||
final resumeTicks = pos == null ? null : ticksFromDuration(pos);
|
||||
|
||||
|
||||
final req = base.copyWith(
|
||||
mediaSourceId: sourceId,
|
||||
startPositionTicks: (resumeTicks != null && resumeTicks > 0)
|
||||
? resumeTicks
|
||||
: null,
|
||||
playFromStart: resumeTicks != null && resumeTicks <= 0,
|
||||
);
|
||||
await _switchTo(req);
|
||||
}
|
||||
|
||||
|
||||
int? _currentResumeTicks() {
|
||||
final pos = _session?.engine.state.position ?? Duration.zero;
|
||||
final ticks = ticksFromDuration(pos);
|
||||
return ticks > 0 ? ticks : null;
|
||||
}
|
||||
|
||||
|
||||
CrossServerSearchKey? _buildCrossServerKey(PlaybackContext ctx) {
|
||||
final activeServerId = ctx.serverId.isEmpty ? null : ctx.serverId;
|
||||
if (activeServerId == null) return null;
|
||||
final providerIds = ctx.providerIds;
|
||||
final type = ctx.itemType;
|
||||
if (type == 'Movie') {
|
||||
if (providerIds.isEmpty && ctx.itemName.isEmpty) return null;
|
||||
return (
|
||||
activeServerId: activeServerId,
|
||||
anchorType: 'Movie',
|
||||
providerIdQuery: encodeProviderIdsForKey(providerIds),
|
||||
seriesProviderIdQuery: '',
|
||||
parentIndexNumber: -1,
|
||||
indexNumber: -1,
|
||||
itemName: ctx.itemName,
|
||||
excludedServerId: ctx.serverId,
|
||||
);
|
||||
}
|
||||
if (type == 'Episode') {
|
||||
final s = ctx.parentIndexNumber;
|
||||
final e = ctx.itemIndexNumber;
|
||||
if (s == null || e == null) return null;
|
||||
final seriesProviderIds = ctx.seriesProviderIds;
|
||||
if (providerIds.isEmpty && seriesProviderIds.isEmpty) return null;
|
||||
return (
|
||||
activeServerId: activeServerId,
|
||||
anchorType: 'Episode',
|
||||
providerIdQuery: encodeProviderIdsForKey(providerIds),
|
||||
seriesProviderIdQuery: encodeProviderIdsForKey(seriesProviderIds),
|
||||
parentIndexNumber: s,
|
||||
indexNumber: e,
|
||||
itemName: ctx.itemName,
|
||||
excludedServerId: ctx.serverId,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
List<CrossServerSourceCard> _filteredCrossServerCards(
|
||||
List<CrossServerSourceCard> cards,
|
||||
PlaybackContext ctx,
|
||||
) {
|
||||
final curServerId = ctx.serverId;
|
||||
final curSourceId = ctx.mediaSourceId;
|
||||
return cards
|
||||
.where(
|
||||
(c) => !(c.serverId == curServerId && c.mediaSourceId == curSourceId),
|
||||
)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _switchToCrossServer(CrossServerSourceCard card) async {
|
||||
if (_switchInFlight) return;
|
||||
_relink.reset();
|
||||
final resumeTicks = _currentResumeTicks();
|
||||
final identity = PlaybackServerIdentity.fromCard(card);
|
||||
|
||||
final oldPlaylist = _playlist;
|
||||
oldPlaylist?.current.removeListener(_onCurrentItemChanged);
|
||||
final newPlaylist = CrossServerPlaylist(
|
||||
sequence: [
|
||||
CrossServerPlaylistEntry(
|
||||
serverId: card.serverId,
|
||||
serverName: card.serverName,
|
||||
serverUrl: card.serverUrl,
|
||||
token: card.token,
|
||||
userId: card.userId,
|
||||
itemId: card.landingItemId,
|
||||
label: card.item.Name,
|
||||
preloadedItem: card.item.Id == card.landingItemId ? card.item : null,
|
||||
),
|
||||
],
|
||||
initialIndex: 0,
|
||||
);
|
||||
_playlist = newPlaylist;
|
||||
try {
|
||||
oldPlaylist?.dispose();
|
||||
} catch (e) {
|
||||
AppLogger.warn(_tag, 'dispose old playlist failed', e);
|
||||
}
|
||||
|
||||
final base = _lastRequest;
|
||||
final req = PlaybackRequest(
|
||||
itemId: card.landingItemId,
|
||||
mediaSourceId: card.mediaSourceId,
|
||||
startPositionTicks: resumeTicks,
|
||||
subtitleSettings: _subtitleSettings,
|
||||
playerSettings: base?.playerSettings ?? _initialPlayerSettings,
|
||||
danmakuSettings: base?.danmakuSettings ?? _initialDanmakuSettings,
|
||||
audioSettings: base?.audioSettings ?? _initialAudioSettings,
|
||||
playFromStart: resumeTicks == null,
|
||||
targetServer: identity,
|
||||
);
|
||||
newPlaylist.current.addListener(_onCurrentItemChanged);
|
||||
await _switchTo(req);
|
||||
}
|
||||
|
||||
|
||||
void _toggleVersionBar() {
|
||||
if (_versionBarOpen.value) {
|
||||
_versionBarOpen.value = false;
|
||||
} else {
|
||||
_drawer.close();
|
||||
_versionBarOpen.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _onAudioTrackSelected(PlaybackSession session, AudioTrack track) {
|
||||
final audioSettings = _lastRequest?.audioSettings ?? _initialAudioSettings;
|
||||
if (!audioSettings.rememberAudioTrack) return;
|
||||
final embyTracks = session.context.embyAudioTracks;
|
||||
if (embyTracks.isEmpty) return;
|
||||
var embyTrack = embyTracks.where((t) => t.index == track.index).firstOrNull;
|
||||
if (embyTrack == null) {
|
||||
final engineTracks = session.engine.state.tracks.embeddedAudio;
|
||||
final pos = engineTracks.indexWhere((t) => t.id == track.id);
|
||||
if (pos >= 0 && pos < embyTracks.length) embyTrack = embyTracks[pos];
|
||||
}
|
||||
if (embyTrack == null) return;
|
||||
unawaited(
|
||||
_audioSettingsNotifier.saveLastAudioTrack(
|
||||
session.context.memoryKey,
|
||||
RememberedAudioTrack(
|
||||
streamIndex: embyTrack.index,
|
||||
language: embyTrack.language,
|
||||
codec: embyTrack.codec,
|
||||
channels: embyTrack.channels,
|
||||
displayTitle: embyTrack.displayTitle,
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void _onSubtitleSelected(PlaybackSession session, int? streamIndex) {
|
||||
unawaited(session.selectSubtitleByStreamIndex(streamIndex));
|
||||
if (streamIndex == null) {
|
||||
unawaited(
|
||||
_subtitleSettingsNotifier.saveLastSubtitleTrack(
|
||||
session.context.memoryKey,
|
||||
RememberedSubtitleTrack(streamIndex: -1, updatedAt: DateTime.now()),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final embyTrack = session.context.embySubtitles
|
||||
.where((track) => track.index == streamIndex)
|
||||
.firstOrNull;
|
||||
if (embyTrack == null) {
|
||||
AppLogger.warn(
|
||||
_tag,
|
||||
'subtitle memory: streamIdx=$streamIndex not in context.embySubtitles; '
|
||||
'skipping memory write',
|
||||
);
|
||||
return;
|
||||
}
|
||||
unawaited(
|
||||
_subtitleSettingsNotifier.saveLastSubtitleTrack(
|
||||
session.context.memoryKey,
|
||||
RememberedSubtitleTrack(
|
||||
streamIndex: embyTrack.index,
|
||||
language: embyTrack.language,
|
||||
title: embyTrack.title,
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user