enable native video player on Android
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d048875490
commit
8d5ccb699e
7 changed files with 72 additions and 11 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue