This commit is contained in:
Paul Chan 2026-06-09 19:07:53 -07:00 • committed by GitHub
commit 3086917095
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 21 deletions

View file

@ -348,28 +348,28 @@ class VideoOverlay extends StatelessWidget {
],
),
Center(
child: Tooltip(
message: videoStore.paused ? 'Play' : 'Pause',
preferBelow: false,
child: IconButton(
iconSize: 56,
icon: Icon(
videoStore.paused
? Icons.play_arrow_rounded
: Icons.pause_rounded,
color: surfaceColor,
shadows: [
Shadow(
offset: const Offset(0, 3),
blurRadius: 8,
color: Colors.black.withValues(alpha: 0.6),
),
],
child: Tooltip(
message: videoStore.paused ? 'Play' : 'Pause',
preferBelow: false,
child: IconButton(
iconSize: 56,
icon: Icon(
videoStore.paused
? Icons.play_arrow_rounded
: Icons.pause_rounded,
color: surfaceColor,
shadows: [
Shadow(
offset: const Offset(0, 3),
blurRadius: 8,
color: Colors.black.withValues(alpha: 0.6),
),
],
),
onPressed: videoStore.handlePausePlay,
),
onPressed: videoStore.handlePausePlay,
),
),
),
Align(
alignment: Alignment.bottomLeft,
child: Row(

View file

@ -95,6 +95,7 @@ abstract class VideoStoreBase with Store {
..addJavaScriptChannel(
'VideoPause',
onMessageReceived: (message) {
_videoInitialized = true;
_paused = true;
if (Platform.isAndroid) pip.setIsPlaying(false);
},
@ -102,6 +103,7 @@ abstract class VideoStoreBase with Store {
..addJavaScriptChannel(
'VideoPlaying',
onMessageReceived: (message) {
_videoInitialized = true;
_paused = false;
if (Platform.isAndroid) pip.setIsPlaying(true);
},
@ -130,6 +132,12 @@ abstract class VideoStoreBase with Store {
)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (progress) {
// Hide Twitch's native overlay before it visibly renders
if (_needsInit && settingsStore.showOverlay) {
_hideDefaultOverlay();
}
},
onPageFinished: (url) async {
if (url != videoUrl) return;
if (!_needsInit) return;
@ -169,6 +177,12 @@ abstract class VideoStoreBase with Store {
/// Disposes the latency settings reaction.
ReactionDisposer? _disposeLatencySettingsReaction;
/// Whether the video player has received its first play/pause event.
///
/// Used to avoid showing Frosty's play button before the stream initializes.
@readonly
var _videoInitialized = false;
/// If the video is currently paused.
///
/// Does not pause or play the video, only used for rendering state of the overlay.
@ -438,7 +452,10 @@ abstract class VideoStoreBase with Store {
.player-controls,
#channel-player-disclosures,
[data-a-target="player-overlay-preview-background"],
[data-a-target="player-overlay-video-stats"] {
[data-a-target="player-overlay-video-stats"],
[data-a-target="player-overlay-play-button"],
[data-a-target="player-overlay-click-handler"],
.player-overlay-background {
display: none !important;
visibility: hidden !important;
pointer-events: none !important;
@ -709,6 +726,11 @@ abstract class VideoStoreBase with Store {
@action
Future<void> initVideo() async {
if (await videoWebViewController.currentUrl() == videoUrl) {
// Hide Twitch's native controls before the video element finishes loading
if (settingsStore.showOverlay) {
await _hideDefaultOverlay();
}
// Declare `window` level utility methods and add event listeners to notify the JavaScript channels when the video plays and pauses.
try {
await videoWebViewController.runJavaScript('''
@ -826,7 +848,6 @@ abstract class VideoStoreBase with Store {
});
''');
if (settingsStore.showOverlay) {
await _hideDefaultOverlay();
// Start latency tracking if either:
// - showLatency is enabled (user wants to see it on overlay), OR
// - autoSyncChatDelay is enabled (needs latency data for syncing)

View file

@ -9,6 +9,26 @@ part of 'video_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$VideoStore on VideoStoreBase, Store {
late final _$_videoInitializedAtom = Atom(
name: 'VideoStoreBase._videoInitialized',
context: context,
);
bool get videoInitialized {
_$_videoInitializedAtom.reportRead();
return super._videoInitialized;
}
@override
bool get _videoInitialized => videoInitialized;
@override
set _videoInitialized(bool value) {
_$_videoInitializedAtom.reportWrite(value, super._videoInitialized, () {
super._videoInitialized = value;
});
}
late final _$_pausedAtom = Atom(
name: 'VideoStoreBase._paused',
context: context,