enable native video player on Android

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tommy Chow 2026-03-08 19:42:53 -07:00
parent d048875490
commit 8d5ccb699e
7 changed files with 72 additions and 11 deletions

View file

@ -79,7 +79,7 @@ class _VideoChatState extends State<VideoChat>
);
late final VideoPlayerInterface _videoStore =
Platform.isIOS && context.settingsStore.useNativePlayer
context.settingsStore.useNativePlayer
? NativeVideoStore(
userLogin: widget.userLogin,
userId: widget.userId,

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:better_native_video_player/better_native_video_player.dart';
import 'package:flutter/material.dart';
@ -13,6 +14,7 @@ import 'package:frosty/screens/settings/stores/auth_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:simple_pip_mode/simple_pip.dart';
part 'native_video_store.g.dart';
@ -43,6 +45,9 @@ abstract class NativeVideoStoreBase with Store implements VideoPlayerInterface {
var _firstTimeSettingQuality = true;
int? _pendingQualityIndex;
final _pip = SimplePip();
ReactionDisposer? _disposeAndroidAutoPipReaction;
StreamSubscription<bool>? _pipSub;
StreamSubscription<List<NativeVideoPlayerQuality>>? _qualitiesSub;
@ -96,6 +101,16 @@ abstract class NativeVideoStoreBase with Store implements VideoPlayerInterface {
_scheduleOverlayHide();
updateStreamInfo();
_initPlayer();
if (Platform.isAndroid) {
_disposeAndroidAutoPipReaction = autorun((_) async {
if (settingsStore.showVideo && await SimplePip.isAutoPipAvailable) {
_pip.setAutoPipMode();
} else {
_pip.setAutoPipMode(autoEnter: false);
}
});
}
}
@action
@ -337,16 +352,20 @@ abstract class NativeVideoStoreBase with Store implements VideoPlayerInterface {
@override
void requestPictureInPicture() {
_controller?.enterPictureInPicture();
if (Platform.isAndroid) {
_pip.enterPipMode(autoEnter: true);
} else {
_controller?.enterPictureInPicture();
}
}
@override
@action
void togglePictureInPicture() {
if (_isInPipMode) {
if (Platform.isIOS && _isInPipMode) {
_controller?.exitPictureInPicture();
} else {
_controller?.enterPictureInPicture();
requestPictureInPicture();
}
}
@ -434,10 +453,17 @@ abstract class NativeVideoStoreBase with Store implements VideoPlayerInterface {
@override
@action
void dispose() {
if (Platform.isAndroid) {
SimplePip.isAutoPipAvailable.then((isAutoPipAvailable) {
if (isAutoPipAvailable) _pip.setAutoPipMode(autoEnter: false);
});
}
_overlayTimer?.cancel();
_latencyTimer?.cancel();
_pipSub?.cancel();
_qualitiesSub?.cancel();
_disposeAndroidAutoPipReaction?.call();
_controller?.removeActivityListener(_handleActivityEvent);
_controller?.dispose();

View file

@ -24,11 +24,11 @@ class VideoSettings extends StatelessWidget {
value: settingsStore.showVideo,
onChanged: (newValue) => settingsStore.showVideo = newValue,
),
if (Platform.isIOS && settingsStore.showVideo)
if (settingsStore.showVideo)
SettingsListSwitch(
title: 'Native player (experimental)',
subtitle: const Text(
'Native video player with auto picture-in-picture and quality selection.',
'More performant video player with auto picture-in-picture and quality selection.',
),
value: settingsStore.useNativePlayer,
onChanged: (newValue) =>
@ -74,7 +74,8 @@ class VideoSettings extends StatelessWidget {
'Allows switching between Twitch\'s overlay and the custom overlay.',
),
value: settingsStore.toggleableOverlay,
onChanged: (newValue) => settingsStore.toggleableOverlay = newValue,
onChanged: (newValue) =>
settingsStore.toggleableOverlay = newValue,
),
SettingsListSwitch(
title: 'Show latency',

View file

@ -1,12 +1,9 @@
import 'dart:io';
import 'package:flutter/services.dart';
class CookieExtractor {
static const _channel = MethodChannel('frosty/cookie_extractor');
static Future<String?> extractTwitchAuthToken() async {
if (!Platform.isIOS) return null;
return await _channel.invokeMethod<String>('extractTwitchAuthToken');
}
}