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:
parent
dc444810b1
commit
fcd7a304e8
5 changed files with 73 additions and 10 deletions
|
|
@ -30,6 +30,8 @@ abstract class NativeVideoStoreBase
|
||||||
with Store
|
with Store
|
||||||
implements NativeVideoPlayerInterface {
|
implements NativeVideoPlayerInterface {
|
||||||
static int _nextId = 0;
|
static int _nextId = 0;
|
||||||
|
static const _legacyWebViewInSettings =
|
||||||
|
'the legacy WebView player in Settings';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String userLogin;
|
final String userLogin;
|
||||||
|
|
@ -465,7 +467,7 @@ abstract class NativeVideoStoreBase
|
||||||
// channel is expected to fail here and the overlay handles it.
|
// channel is expected to fail here and the overlay handles it.
|
||||||
if (_streamInfo != null) {
|
if (_streamInfo != null) {
|
||||||
_showErrorOverlay(
|
_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();
|
_stallRecoveryTimer?.cancel();
|
||||||
final errorMessage =
|
final errorMessage =
|
||||||
eventData?['message'] as String? ??
|
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 errorCode = eventData?['code'] as String?;
|
||||||
final statusCode = eventData?['statusCode'] as int?;
|
final statusCode = eventData?['statusCode'] as int?;
|
||||||
|
|
||||||
|
|
@ -751,8 +753,8 @@ abstract class NativeVideoStoreBase
|
||||||
_loading = false;
|
_loading = false;
|
||||||
_showErrorOverlay(
|
_showErrorOverlay(
|
||||||
bouncing
|
bouncing
|
||||||
? 'Twitch is having trouble with this channel right now. Try again in a moment, 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 the standard player in Settings.',
|
: 'Stream stalled. Try refreshing or switch to $_legacyWebViewInSettings.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@ part 'settings_store.g.dart';
|
||||||
class SettingsStore extends _SettingsStoreBase with _$SettingsStore {
|
class SettingsStore extends _SettingsStoreBase with _$SettingsStore {
|
||||||
SettingsStore();
|
SettingsStore();
|
||||||
|
|
||||||
factory SettingsStore.fromJson(Map<String, dynamic> json) =>
|
factory SettingsStore.fromJson(Map<String, dynamic> json) {
|
||||||
_$SettingsStoreFromJson(json);
|
final store = _$SettingsStoreFromJson(json);
|
||||||
|
store._migrateNativePlayerDefault();
|
||||||
|
return store;
|
||||||
|
}
|
||||||
Map<String, dynamic> toJson() => _$SettingsStoreToJson(this);
|
Map<String, dynamic> toJson() => _$SettingsStoreToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +68,7 @@ abstract class _SettingsStoreBase with Store {
|
||||||
static const defaultShowVideo = true;
|
static const defaultShowVideo = true;
|
||||||
static const defaultDefaultToHighestQuality = false;
|
static const defaultDefaultToHighestQuality = false;
|
||||||
static const defaultUseTextureRendering = true;
|
static const defaultUseTextureRendering = true;
|
||||||
static const defaultUseNativePlayer = false;
|
static const defaultUseNativePlayer = true;
|
||||||
|
|
||||||
static const defaultShowOverlay = true;
|
static const defaultShowOverlay = true;
|
||||||
static const defaultToggleableOverlay = false;
|
static const defaultToggleableOverlay = false;
|
||||||
|
|
@ -88,6 +91,19 @@ abstract class _SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
var useNativePlayer = defaultUseNativePlayer;
|
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
|
// Overlay options
|
||||||
@JsonKey(defaultValue: defaultShowOverlay)
|
@JsonKey(defaultValue: defaultShowOverlay)
|
||||||
@observable
|
@observable
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ SettingsStore _$SettingsStoreFromJson(
|
||||||
..showVideo = json['showVideo'] as bool? ?? true
|
..showVideo = json['showVideo'] as bool? ?? true
|
||||||
..defaultToHighestQuality = json['defaultToHighestQuality'] as bool? ?? false
|
..defaultToHighestQuality = json['defaultToHighestQuality'] as bool? ?? false
|
||||||
..useTextureRendering = json['useTextureRendering'] as bool? ?? true
|
..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
|
..showOverlay = json['showOverlay'] as bool? ?? true
|
||||||
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
|
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
|
||||||
..showLatency = json['showLatency'] as bool? ?? false
|
..showLatency = json['showLatency'] as bool? ?? false
|
||||||
|
|
@ -106,6 +107,7 @@ Map<String, dynamic> _$SettingsStoreToJson(
|
||||||
'defaultToHighestQuality': instance.defaultToHighestQuality,
|
'defaultToHighestQuality': instance.defaultToHighestQuality,
|
||||||
'useTextureRendering': instance.useTextureRendering,
|
'useTextureRendering': instance.useTextureRendering,
|
||||||
'useNativePlayer': instance.useNativePlayer,
|
'useNativePlayer': instance.useNativePlayer,
|
||||||
|
'nativePlayerV52Migrated': instance.nativePlayerV52Migrated,
|
||||||
'showOverlay': instance.showOverlay,
|
'showOverlay': instance.showOverlay,
|
||||||
'toggleableOverlay': instance.toggleableOverlay,
|
'toggleableOverlay': instance.toggleableOverlay,
|
||||||
'showLatency': instance.showLatency,
|
'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
|
@override
|
||||||
void resetVideoSettings() {
|
void resetVideoSettings() {
|
||||||
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
|
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ class VideoSettings extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (settingsStore.showVideo)
|
if (settingsStore.showVideo)
|
||||||
SettingsListSwitch(
|
SettingsListSwitch(
|
||||||
title: 'Native player (experimental)',
|
title: 'Native player',
|
||||||
subtitle: const Text(
|
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,
|
value: settingsStore.useNativePlayer,
|
||||||
onChanged: (newValue) =>
|
onChanged: (newValue) =>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ void main() {
|
||||||
expect(store.showVideo, isTrue);
|
expect(store.showVideo, isTrue);
|
||||||
expect(store.defaultToHighestQuality, isFalse);
|
expect(store.defaultToHighestQuality, isFalse);
|
||||||
expect(store.useTextureRendering, isTrue);
|
expect(store.useTextureRendering, isTrue);
|
||||||
|
expect(store.useNativePlayer, isTrue);
|
||||||
expect(store.showOverlay, isTrue);
|
expect(store.showOverlay, isTrue);
|
||||||
expect(store.toggleableOverlay, isFalse);
|
expect(store.toggleableOverlay, isFalse);
|
||||||
expect(store.showLatency, isFalse);
|
expect(store.showLatency, isFalse);
|
||||||
|
|
@ -102,6 +103,34 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('native player default migration', () {
|
||||||
|
test('5.1.0 WebView default is flipped on once', () {
|
||||||
|
final store = SettingsStore.fromJson({'useNativePlayer': false});
|
||||||
|
|
||||||
|
expect(store.useNativePlayer, isTrue);
|
||||||
|
expect(store.nativePlayerV52Migrated, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('opt-out is kept after the one-shot migration', () {
|
||||||
|
final store = SettingsStore.fromJson({
|
||||||
|
'useNativePlayer': false,
|
||||||
|
'nativePlayerV52Migrated': true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.useNativePlayer, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('roundtrip after opt-out stays on WebView', () {
|
||||||
|
final store = SettingsStore.fromJson({});
|
||||||
|
store.useNativePlayer = false;
|
||||||
|
|
||||||
|
final restored = SettingsStore.fromJson(store.toJson());
|
||||||
|
|
||||||
|
expect(restored.useNativePlayer, isFalse);
|
||||||
|
expect(restored.nativePlayerV52Migrated, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('SettingsStore reset actions', () {
|
group('SettingsStore reset actions', () {
|
||||||
test('resetGeneralSettings restores general defaults', () {
|
test('resetGeneralSettings restores general defaults', () {
|
||||||
final store = SettingsStore.fromJson({});
|
final store = SettingsStore.fromJson({});
|
||||||
|
|
@ -127,6 +156,7 @@ void main() {
|
||||||
store.showVideo = false;
|
store.showVideo = false;
|
||||||
store.defaultToHighestQuality = true;
|
store.defaultToHighestQuality = true;
|
||||||
store.useTextureRendering = false;
|
store.useTextureRendering = false;
|
||||||
|
store.useNativePlayer = false;
|
||||||
store.showOverlay = false;
|
store.showOverlay = false;
|
||||||
store.toggleableOverlay = true;
|
store.toggleableOverlay = true;
|
||||||
store.showLatency = true;
|
store.showLatency = true;
|
||||||
|
|
@ -136,6 +166,7 @@ void main() {
|
||||||
expect(store.showVideo, isTrue);
|
expect(store.showVideo, isTrue);
|
||||||
expect(store.defaultToHighestQuality, isFalse);
|
expect(store.defaultToHighestQuality, isFalse);
|
||||||
expect(store.useTextureRendering, isTrue);
|
expect(store.useTextureRendering, isTrue);
|
||||||
|
expect(store.useNativePlayer, isTrue);
|
||||||
expect(store.showOverlay, isTrue);
|
expect(store.showOverlay, isTrue);
|
||||||
expect(store.toggleableOverlay, isFalse);
|
expect(store.toggleableOverlay, isFalse);
|
||||||
expect(store.showLatency, isFalse);
|
expect(store.showLatency, isFalse);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue