fix(player): 隔离播放资源所有权并修复黑屏
This commit is contained in:
1 parent
54eb5a60a8
commit
bb2c729a7e
9 files changed
+223
-25
No files matched your search
@@ -15,6 +15,16 @@ class PlaybackCoordinator {
|
||||
if (!valid()) return false;
|
||||
if (_owner != session) {
|
||||
final previous = _owner;
|
||||
if (previous != null && previous.owner != session.owner) {
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-owner] phase=reject-coordinator-handoff '
|
||||
'owner=${session.owner.name} '
|
||||
'activeOwner=${previous.owner.name}',
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
await previous?._releasePlayers();
|
||||
if (previous != null && previous.livePlayerCount != 0) return false;
|
||||
_owner = null;
|
||||
@@ -56,6 +66,10 @@ Future<void> _parkNeighbors(
|
||||
|
||||
Future<void> _placeNeighbor(PlaybackSession session, _PlayerSlot? slot) async {
|
||||
if (slot == null || slot == session._active || slot.retiring) return;
|
||||
if (slot.owner != session.owner) {
|
||||
session._logOwnerRejection('park', slot);
|
||||
return;
|
||||
}
|
||||
if (session._next == null && slot.index == session._nextIndex) {
|
||||
session._next = slot;
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,8 @@ typedef PlayerFactory = PlayerPort Function(Episode episode);
|
||||
typedef PlayerPreparation =
|
||||
Future<void> Function(Episode episode, PlayerPort player);
|
||||
|
||||
enum PlaybackSessionOwner { home, player }
|
||||
|
||||
/// Owns current plus previous/next frames; allocation stays serialized.
|
||||
class PlaybackSession extends ChangeNotifier {
|
||||
PlaybackSession({
|
||||
@@ -27,6 +29,8 @@ class PlaybackSession extends ChangeNotifier {
|
||||
this.activate,
|
||||
PlaybackCoordinator? coordinator,
|
||||
this.waitForFrame,
|
||||
this.owner = PlaybackSessionOwner.player,
|
||||
this.dramaIdAt,
|
||||
}) : _coordinator = coordinator ?? PlaybackCoordinator();
|
||||
|
||||
final List<Episode> episodes;
|
||||
@@ -39,6 +43,8 @@ class PlaybackSession extends ChangeNotifier {
|
||||
/// Runs only when a slot becomes current (e.g. progress listeners).
|
||||
final PlayerPreparation? activate;
|
||||
final Future<void> Function()? waitForFrame;
|
||||
final PlaybackSessionOwner owner;
|
||||
final String? Function(int index)? dramaIdAt;
|
||||
final PlaybackCoordinator _coordinator;
|
||||
final Set<_PlayerSlot> _live = {};
|
||||
_PlayerSlot? _active;
|
||||
@@ -161,7 +167,12 @@ class PlaybackSession extends ChangeNotifier {
|
||||
if (_live.length >= maxLivePlayers) {
|
||||
throw StateError('Playback resource limit reached');
|
||||
}
|
||||
final slot = _PlayerSlot(index, factory(episodes[index]));
|
||||
final slot = _PlayerSlot(
|
||||
index,
|
||||
factory(episodes[index]),
|
||||
owner: owner,
|
||||
dramaId: dramaIdAt?.call(index),
|
||||
);
|
||||
_live.add(slot);
|
||||
slot.ready = () async {
|
||||
try {
|
||||
@@ -180,7 +191,19 @@ class PlaybackSession extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> _retire(_PlayerSlot? slot) async {
|
||||
if (slot == null || !_live.contains(slot)) return;
|
||||
if (slot == null) return;
|
||||
if (slot.owner != owner) {
|
||||
_logOwnerRejection('retire', slot);
|
||||
return;
|
||||
}
|
||||
if (!_live.contains(slot)) return;
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-owner] phase=retire owner=${slot.owner.name} '
|
||||
'drama=${slot.dramaId ?? '-'} episodeIndex=${slot.index} '
|
||||
'controller=${_playerIdentity(slot.player)}',
|
||||
);
|
||||
}
|
||||
slot.retiring = true;
|
||||
final cleared =
|
||||
_active == slot ||
|
||||
@@ -460,6 +483,15 @@ class PlaybackSession extends ChangeNotifier {
|
||||
if (!_closed) notifyListeners();
|
||||
}
|
||||
|
||||
void _logOwnerRejection(String phase, _PlayerSlot slot) {
|
||||
if (!kDebugMode) return;
|
||||
debugPrint(
|
||||
'[player-owner] phase=reject-$phase expected=${owner.name} '
|
||||
'actual=${slot.owner.name} drama=${slot.dramaId ?? '-'} '
|
||||
'episodeIndex=${slot.index} controller=${_playerIdentity(slot.player)}',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
if (_closed) return;
|
||||
_closed = true;
|
||||
@@ -470,9 +502,16 @@ class PlaybackSession extends ChangeNotifier {
|
||||
}
|
||||
|
||||
class _PlayerSlot {
|
||||
_PlayerSlot(this.index, this.player);
|
||||
_PlayerSlot(
|
||||
this.index,
|
||||
this.player, {
|
||||
required this.owner,
|
||||
required this.dramaId,
|
||||
});
|
||||
int index;
|
||||
final PlayerPort player;
|
||||
final PlaybackSessionOwner owner;
|
||||
final String? dramaId;
|
||||
late final Future<void> ready;
|
||||
bool prepared = false;
|
||||
bool activated = false;
|
||||
@@ -481,3 +520,9 @@ class _PlayerSlot {
|
||||
bool reusedWarm = false;
|
||||
Object? failure;
|
||||
}
|
||||
|
||||
String _playerIdentity(PlayerPort player) {
|
||||
return player is VideoPlayerPort
|
||||
? '${identityHashCode(player.controller)}'
|
||||
: '${identityHashCode(player)}';
|
||||
}
|
||||
Reference in new issue
Block a user