Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 54 additions & 21 deletions lib/player_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class _PlayerScreenState extends State<PlayerScreen>
bool _fullscreen = false;
bool _automaticFullscreenSuppressed = false;
bool _panelOpen = false;
bool _panelBackGuard = false;
Timer? _panelBackGuardTimer;
int _mobileTab = 0;
bool _autoAdvance = true;
bool? _systemFullscreen;
Expand Down Expand Up @@ -182,15 +184,13 @@ class _PlayerScreenState extends State<PlayerScreen>
widget.store.addListener(_accessChanged);
_player =
widget.playerFactory?.call() ??
(Platform.isAndroid
? LunaExoPlayer()
: Player(
configuration: const PlayerConfiguration(
bufferSize: 32 * 1024 * 1024,
logLevel: MPVLogLevel.error,
),
));
_video = widget.videoBuilder == null && !Platform.isAndroid
Player(
configuration: const PlayerConfiguration(
bufferSize: 32 * 1024 * 1024,
logLevel: MPVLogLevel.error,
),
);
_video = widget.videoBuilder == null
? VideoController(
_player,
configuration: VideoControllerConfiguration(
Expand Down Expand Up @@ -1014,8 +1014,14 @@ class _PlayerScreenState extends State<PlayerScreen>
await platform.setProperty('vd-lavc-skiploopfilter', 'all');
await platform.setProperty('vd-lavc-skipidct', 'all');
await platform.setProperty('vd-lavc-threads', '2');
await platform.setProperty('demuxer-max-bytes', '${4 * 1024 * 1024}');
await platform.setProperty('demuxer-max-back-bytes', '${1 * 1024 * 1024}');
await platform.setProperty(
'demuxer-max-bytes',
'${4 * 1024 * 1024}',
);
await platform.setProperty(
'demuxer-max-back-bytes',
'${1 * 1024 * 1024}',
);
await platform.setProperty('demuxer-readahead-secs', '5');
} else {
await platform.setProperty('hwdec', 'auto-safe');
Expand Down Expand Up @@ -1045,7 +1051,9 @@ class _PlayerScreenState extends State<PlayerScreen>
_plan = plan;
installed = true;
_acceptErrors = true;
DiaryService.add('[Play] 调用 _player.open: url=${plan.url}, headers=${plan.headers.keys.toList()}');
DiaryService.add(
'[Play] 调用 _player.open: url=${plan.url}, headers=${plan.headers.keys.toList()}',
);
await _player.open(
Media(
plan.url,
Expand Down Expand Up @@ -1379,6 +1387,7 @@ class _PlayerScreenState extends State<PlayerScreen>
),
);
} finally {
_guardPanelBack();
if (mounted && !_closed) setState(() => _panelOpen = false);
}
if (index != null && mounted && !_closed && index != _index) {
Expand Down Expand Up @@ -1420,6 +1429,7 @@ class _PlayerScreenState extends State<PlayerScreen>
),
);
} finally {
_guardPanelBack();
if (mounted && !_closed) setState(() => _panelOpen = false);
}
if (selection == null || !mounted || _closed) return;
Expand All @@ -1439,17 +1449,35 @@ class _PlayerScreenState extends State<PlayerScreen>
}
}

void _guardPanelBack() {
_panelBackGuard = true;
_panelBackGuardTimer?.cancel();
_panelBackGuardTimer = Timer(const Duration(milliseconds: 500), () {
_panelBackGuard = false;
});
}

void _back() {
if (_panelBackGuard) return;
if (_panelOpen) {
_guardPanelBack();
if (!(ModalRoute.of(context)?.isCurrent ?? true)) {
Navigator.of(context).pop();
}
return;
}
if (!(ModalRoute.of(context)?.isCurrent ?? true)) return;
if (_showFullscreen && !_television) {
_rotate();
} else {
Navigator.of(context).maybePop();
Navigator.of(context).pop();
}
}

@override
void dispose() {
_closed = true;
_panelBackGuardTimer?.cancel();
final enhancementClosed = _enhancement.close();
LanController.current?.detachPlayback(_lanIdentity);
widget.handoff?.fail('接收端已退出播放');
Expand Down Expand Up @@ -1525,11 +1553,9 @@ class _PlayerScreenState extends State<PlayerScreen>
final fullscreen = _showFullscreen;
final pictureInPicture = _pictureInPictureVisible;
return PopScope(
canPop: _television || !fullscreen,
canPop: !_television && !fullscreen,
onPopInvokedWithResult: (didPop, result) {
if (!didPop && fullscreen && !_television) {
_rotate();
}
if (!didPop) _back();
},
child: CallbackShortcuts(
bindings: {
Expand Down Expand Up @@ -1737,8 +1763,14 @@ class _PlayerScreenState extends State<PlayerScreen>
Video(
controller: _video!,
fit: BoxFit.contain,
controls: (_) => layeredControls,
controls: _television
? NoVideoControls
: (_) => layeredControls,
),
if (_television &&
widget.videoBuilder == null &&
_player is! LunaExoPlayer)
layeredControls,
if (_loading && !hideOverlayForPictureInPicture)
ColoredBox(
color: Colors.black.withValues(alpha: .78),
Expand Down Expand Up @@ -1768,7 +1800,8 @@ class _PlayerScreenState extends State<PlayerScreen>
crossAxisAlignment: WrapCrossAlignment.center,
children: [
FilledButton.tonalIcon(
onPressed: () => DiaryService.showDiaryDialog(context),
onPressed: () =>
DiaryService.showDiaryDialog(context),
icon: const Icon(Icons.receipt_long_rounded),
label: const Text('查看播放日记'),
),
Expand All @@ -1779,8 +1812,8 @@ class _PlayerScreenState extends State<PlayerScreen>
label: const Text('改为在线播放'),
)
else if (!_localFailure &&
!widget.localOnly &&
widget.repository.supportsSourceManagement)
!widget.localOnly &&
widget.repository.supportsSourceManagement)
SourceDiagnosticsButton(
repository: widget.repository,
store: widget.store,
Expand Down
26 changes: 19 additions & 7 deletions lib/remote_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -369,13 +369,25 @@ class _RemoteGridState extends State<RemoteGrid> {
crossAxisSpacing: widget.spacing,
mainAxisSpacing: widget.spacing,
),
delegate: SliverChildBuilderDelegate(
(context, index) =>
widget.itemBuilder(context, index, _node(index), () {
_focused = widget.itemKeys[index];
}),
childCount: widget.itemKeys.length,
),
delegate: SliverChildBuilderDelegate((context, index) {
final node = _node(index);
final key = widget.itemKeys[index];
if (_target == key) {
final generation = _generation;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted &&
generation == _generation &&
_target == key &&
node.context != null) {
node.requestFocus();
_target = null;
}
});
}
return widget.itemBuilder(context, index, node, () {
_focused = key;
});
}, childCount: widget.itemKeys.length),
),
),
if (widget.footer != null) SliverToBoxAdapter(child: widget.footer),
Expand Down
1 change: 1 addition & 0 deletions lib/television_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ class _TelevisionControlsState extends State<TelevisionControls> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.w600,
),
Expand Down
56 changes: 56 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
dbus:
dependency: transitive
description:
Expand Down Expand Up @@ -247,6 +255,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
html:
dependency: transitive
description:
name: html
sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699"
url: "https://pub.dev"
source: hosted
version: "0.15.7"
http:
dependency: transitive
description:
Expand Down Expand Up @@ -864,6 +880,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.3"
video_player:
dependency: "direct main"
description:
name: video_player
sha256: "07ae1e03f2d4c8e2e9a75ee678f90acbc4506d4458a9c628512d119a1eee5754"
url: "https://pub.dev"
source: hosted
version: "2.14.1"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: d27054dea34d748a44f06d433a57005d2aac69944485b0abbaed3303dbcfa3f1
url: "https://pub.dev"
source: hosted
version: "2.12.2"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: "2fc56e537324a19eb649fca991aa308ac961b5406292876fa657b062de461fd8"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce"
url: "https://pub.dev"
source: hosted
version: "6.9.0"
video_player_web:
dependency: transitive
description:
name: video_player_web
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
vm_service:
dependency: transitive
description:
Expand Down
27 changes: 27 additions & 0 deletions test/television_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,30 @@ void main() {
},
);

testWidgets('TV panel back does not also exit playback', (tester) async {
final repository = RouteRepository();
final player = ScriptedPlayer();
await mount(tester, repository, player);
for (final panel in ['tv-episodes', 'tv-settings']) {
await tester.tap(find.byKey(ValueKey(panel)));
await settle(tester);
expect(find.byType(AlertDialog), findsOneWidget);
await tester.sendKeyEvent(
LogicalKeyboardKey.goBack,
physicalKey: PhysicalKeyboardKey.escape,
);
await settle(tester);
await tester.binding.handlePopRoute();
await settle(tester);
expect(find.byType(PlayerScreen), findsOneWidget);
expect(player.disposed, isFalse);
await tester.pump(const Duration(milliseconds: 600));
await settle(tester);
expect(find.byType(AlertDialog), findsNothing);
}
await leave(tester, repository, player);
});

testWidgets(
'TV selection restores the current episode and panels keep focus during playback',
(tester) async {
Expand All @@ -149,6 +173,7 @@ void main() {
);
expect(repository.active.length, 1);
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.select);
expect(find.text('倍速'), findsOneWidget);
await press(tester, LogicalKeyboardKey.arrowRight);
Expand All @@ -174,6 +199,8 @@ void main() {
FocusManager.instance.primaryFocus?.debugLabel,
'tv-player-settings',
);
await tester.pump(const Duration(milliseconds: 600));
await settle(tester);
await leave(tester, repository, player);
},
);
Expand Down