feat(video): make the native player the default

5.1.0 already serialized useNativePlayer: false, so existing installs
need a one-shot migrate. WebView stays as a legacy opt-out.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Tommy Chow 2026-08-16 12:21:44 -07:00
parent dc444810b1
commit fcd7a304e8
5 changed files with 73 additions and 10 deletions

View file

@ -30,6 +30,8 @@ abstract class NativeVideoStoreBase
with Store
implements NativeVideoPlayerInterface {
static int _nextId = 0;
static const _legacyWebViewInSettings =
'the legacy WebView player in Settings';
@override
final String userLogin;
@ -465,7 +467,7 @@ abstract class NativeVideoStoreBase
// channel is expected to fail here and the overlay handles it.
if (_streamInfo != null) {
_showErrorOverlay(
'Native player failed to load. Try the standard player in Settings.',
'Native player failed to load. Try $_legacyWebViewInSettings.',
);
}
});
@ -560,7 +562,7 @@ abstract class NativeVideoStoreBase
_stallRecoveryTimer?.cancel();
final errorMessage =
eventData?['message'] as String? ??
'Playback error. Try refreshing or switch to the standard player in Settings.';
'Playback error. Try refreshing or switch to $_legacyWebViewInSettings.';
final errorCode = eventData?['code'] as String?;
final statusCode = eventData?['statusCode'] as int?;
@ -751,8 +753,8 @@ abstract class NativeVideoStoreBase
_loading = false;
_showErrorOverlay(
bouncing
? 'Twitch is having trouble with this channel right now. Try again in a moment, or switch to the standard player in Settings.'
: 'Stream stalled. Try refreshing or switch to the standard player in Settings.',
? 'Twitch is having trouble with this channel right now. Try again in a moment, or switch to $_legacyWebViewInSettings.'
: 'Stream stalled. Try refreshing or switch to $_legacyWebViewInSettings.',
);
});
}

View file

@ -8,8 +8,11 @@ part 'settings_store.g.dart';
class SettingsStore extends _SettingsStoreBase with _$SettingsStore {
SettingsStore();
factory SettingsStore.fromJson(Map<String, dynamic> json) =>
_$SettingsStoreFromJson(json);
factory SettingsStore.fromJson(Map<String, dynamic> json) {
final store = _$SettingsStoreFromJson(json);
store._migrateNativePlayerDefault();
return store;
}
Map<String, dynamic> toJson() => _$SettingsStoreToJson(this);
}
@ -65,7 +68,7 @@ abstract class _SettingsStoreBase with Store {
static const defaultShowVideo = true;
static const defaultDefaultToHighestQuality = false;
static const defaultUseTextureRendering = true;
static const defaultUseNativePlayer = false;
static const defaultUseNativePlayer = true;
static const defaultShowOverlay = true;
static const defaultToggleableOverlay = false;
@ -88,6 +91,19 @@ abstract class _SettingsStoreBase with Store {
@observable
var useNativePlayer = defaultUseNativePlayer;
/// 5.2.0 made native the default. 5.1.0 already wrote
/// `useNativePlayer: false` into saved settings, so a Dart default
/// change alone would leave existing installs on WebView.
@JsonKey(defaultValue: false)
var nativePlayerV52Migrated = false;
@action
void _migrateNativePlayerDefault() {
if (nativePlayerV52Migrated) return;
useNativePlayer = true;
nativePlayerV52Migrated = true;
}
// Overlay options
@JsonKey(defaultValue: defaultShowOverlay)
@observable

View file

@ -23,7 +23,8 @@ SettingsStore _$SettingsStoreFromJson(
..showVideo = json['showVideo'] as bool? ?? true
..defaultToHighestQuality = json['defaultToHighestQuality'] as bool? ?? false
..useTextureRendering = json['useTextureRendering'] as bool? ?? true
..useNativePlayer = json['useNativePlayer'] as bool? ?? false
..useNativePlayer = json['useNativePlayer'] as bool? ?? true
..nativePlayerV52Migrated = json['nativePlayerV52Migrated'] as bool? ?? false
..showOverlay = json['showOverlay'] as bool? ?? true
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
..showLatency = json['showLatency'] as bool? ?? false
@ -106,6 +107,7 @@ Map<String, dynamic> _$SettingsStoreToJson(
'defaultToHighestQuality': instance.defaultToHighestQuality,
'useTextureRendering': instance.useTextureRendering,
'useNativePlayer': instance.useNativePlayer,
'nativePlayerV52Migrated': instance.nativePlayerV52Migrated,
'showOverlay': instance.showOverlay,
'toggleableOverlay': instance.toggleableOverlay,
'showLatency': instance.showLatency,
@ -1159,6 +1161,18 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
}
@override
void _migrateNativePlayerDefault() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase._migrateNativePlayerDefault',
);
try {
return super._migrateNativePlayerDefault();
} finally {
_$_SettingsStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
void resetVideoSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(

View file

@ -26,9 +26,9 @@ class VideoSettings extends StatelessWidget {
),
if (settingsStore.showVideo)
SettingsListSwitch(
title: 'Native player (experimental)',
title: 'Native player',
subtitle: const Text(
'More performant video player with auto picture-in-picture and quality selection.',
'Picture-in-Picture, quality selection, and lower latency. Turn off to use the legacy WebView player.',
),
value: settingsStore.useNativePlayer,
onChanged: (newValue) =>