diff --git a/android/app/src/main/kotlin/com/tommychow/frosty/CookieExtractorPlugin.kt b/android/app/src/main/kotlin/com/tommychow/frosty/CookieExtractorPlugin.kt new file mode 100644 index 00000000..055101d0 --- /dev/null +++ b/android/app/src/main/kotlin/com/tommychow/frosty/CookieExtractorPlugin.kt @@ -0,0 +1,32 @@ +package com.tommychow.frosty + +import android.webkit.CookieManager +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class CookieExtractorPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { + private lateinit var channel: MethodChannel + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(binding.binaryMessenger, "frosty/cookie_extractor") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + if (call.method == "extractTwitchAuthToken") { + val cookies = CookieManager.getInstance().getCookie("https://twitch.tv") + val authToken = cookies?.split(";") + ?.map { it.trim() } + ?.firstOrNull { it.startsWith("auth-token=") } + ?.substringAfter("auth-token=") + result.success(authToken) + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/android/app/src/main/kotlin/com/tommychow/frosty/MainActivity.kt b/android/app/src/main/kotlin/com/tommychow/frosty/MainActivity.kt index a7791025..30e59dc7 100644 --- a/android/app/src/main/kotlin/com/tommychow/frosty/MainActivity.kt +++ b/android/app/src/main/kotlin/com/tommychow/frosty/MainActivity.kt @@ -1,6 +1,11 @@ package com.tommychow.frosty import cl.puntito.simple_pip_mode.PipCallbackHelperActivityWrapper +import io.flutter.embedding.engine.FlutterEngine class MainActivity: PipCallbackHelperActivityWrapper() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + flutterEngine.plugins.add(CookieExtractorPlugin()) + } } diff --git a/lib/screens/channel/channel.dart b/lib/screens/channel/channel.dart index 468dca06..a3bd16d4 100644 --- a/lib/screens/channel/channel.dart +++ b/lib/screens/channel/channel.dart @@ -79,7 +79,7 @@ class _VideoChatState extends State ); late final VideoPlayerInterface _videoStore = - Platform.isIOS && context.settingsStore.useNativePlayer + context.settingsStore.useNativePlayer ? NativeVideoStore( userLogin: widget.userLogin, userId: widget.userId, diff --git a/lib/screens/channel/video/native_video_store.dart b/lib/screens/channel/video/native_video_store.dart index 0ef29efd..da830918 100644 --- a/lib/screens/channel/video/native_video_store.dart +++ b/lib/screens/channel/video/native_video_store.dart @@ -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? _pipSub; StreamSubscription>? _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(); diff --git a/lib/screens/settings/video_settings.dart b/lib/screens/settings/video_settings.dart index f7928a36..714694d9 100644 --- a/lib/screens/settings/video_settings.dart +++ b/lib/screens/settings/video_settings.dart @@ -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', diff --git a/lib/services/cookie_extractor.dart b/lib/services/cookie_extractor.dart index 75315a1e..cca2b028 100644 --- a/lib/services/cookie_extractor.dart +++ b/lib/services/cookie_extractor.dart @@ -1,12 +1,9 @@ -import 'dart:io'; - import 'package:flutter/services.dart'; class CookieExtractor { static const _channel = MethodChannel('frosty/cookie_extractor'); static Future extractTwitchAuthToken() async { - if (!Platform.isIOS) return null; return await _channel.invokeMethod('extractTwitchAuthToken'); } } diff --git a/pubspec.lock b/pubspec.lock index 05f7a872..397a0565 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -102,7 +102,7 @@ packages: description: path: "." ref: frosty-patches - resolved-ref: "1a1330c815f09f1b3ff2ca11442276315f12030c" + resolved-ref: "72ef59c95ef282029c2ac6d0bd782f153f2fd9ef" url: "https://github.com/tommyxchow/flutter-native-video-player.git" source: git version: "0.4.11"