@@ -8,6 +8,8 @@ import 'package:rxdart/rxdart.dart';
88import 'unified_player_interface.dart' ;
99import 'package:floating/floating.dart' ;
1010import 'package:pure_live/common/index.dart' ;
11+ import 'package:pure_live/routes/app_navigation.dart' ;
12+ import 'package:flutter_floating/flutter_floating.dart' ;
1113import 'package:pure_live/common/global/platform_utils.dart' ;
1214
1315enum PlayerEngine { mediaKit, fijk }
@@ -28,7 +30,8 @@ class SwitchableGlobalPlayer {
2830 late Floating floating;
2931 bool playerHasInit = false ;
3032 bool hasSetVolume = false ;
31-
33+ static const String _floatTag = "global_video_player" ;
34+ final isFloating = false .obs;
3235 // 依赖
3336 final SettingsService settings = Get .find <SettingsService >();
3437
@@ -55,6 +58,8 @@ class SwitchableGlobalPlayer {
5558 Stream <int ?> get width => _currentPlayer? .width ?? Stream .value (null );
5659 Stream <int ?> get height => _currentPlayer? .height ?? Stream .value (null );
5760
61+ // 全局floating
62+ late LiveRoom currentFloatRoom;
5863 Future <void > init (PlayerEngine engine) async {
5964 if (_currentPlayer != null ) return ;
6065 _currentPlayer = _createPlayer (engine);
@@ -130,6 +135,88 @@ class SwitchableGlobalPlayer {
130135 }
131136 }
132137
138+ /// 构建悬浮窗关闭按钮
139+ Widget _buildCloseButton () {
140+ return GestureDetector (
141+ onTap: () {
142+ stop ();
143+ closeAppFloating ();
144+ },
145+ child: Container (
146+ padding: const EdgeInsets .all (4 ),
147+ decoration: BoxDecoration (
148+ color: Colors .black.withAlpha (128 ),
149+ shape: BoxShape .circle,
150+ border: Border .all (color: Colors .white24, width: 0.5 ),
151+ ),
152+ child: const Icon (Icons .close, color: Colors .white, size: 16 ),
153+ ),
154+ );
155+ }
156+
157+ void showAppFloating (LiveRoom room) {
158+ floatingManager.disposeFloating (_floatTag);
159+ double baseWidth = Platform .isWindows ? 300.0 : 200.0 ;
160+ double ratio = isVerticalVideo.value ? (9 / 16 ) : (16 / 9 );
161+ double floatHeight = baseWidth / ratio;
162+ floatingManager.createFloating (
163+ _floatTag,
164+ FloatingOverlay (
165+ Container (
166+ width: baseWidth,
167+ height: floatHeight,
168+ clipBehavior: Clip .antiAlias,
169+ decoration: BoxDecoration (
170+ borderRadius: BorderRadius .circular (12 ),
171+ color: Colors .black,
172+ boxShadow: [
173+ BoxShadow (
174+ color: Colors .black.withAlpha (100 ), // 加深一点阴影
175+ blurRadius: 15 ,
176+ spreadRadius: 2 ,
177+ offset: const Offset (0 , 4 ),
178+ ),
179+ ],
180+ ),
181+ child: Stack (
182+ children: [
183+ getVideoWidget (null ),
184+ Positioned .fill (
185+ child: GestureDetector (
186+ behavior: HitTestBehavior .opaque,
187+ onTap: () {
188+ // 点击悬浮窗回到页面的逻辑
189+ closeAppFloating ();
190+ AppNavigator .toLiveRoomDetail (liveRoom: currentFloatRoom);
191+ },
192+ child: const SizedBox .expand (),
193+ ),
194+ ),
195+
196+ // 3. 顶层:关闭按钮
197+ Positioned (right: 8 , top: 8 , child: _buildCloseButton ()),
198+ ],
199+ ),
200+ ),
201+ right: 50 ,
202+ top: 100 ,
203+ slideType: FloatingEdgeType .onRightAndTop,
204+ params: FloatingParams (isSnapToEdge: false , snapToEdgeSpace: 10 , dragOpacity: 0.8 ),
205+ ),
206+ );
207+
208+ floatingManager.getFloating (_floatTag).open (Get .context! );
209+ currentFloatRoom = room;
210+ isFloating.value = true ;
211+ }
212+
213+ /// 关闭并销毁悬浮播放器
214+ void closeAppFloating () {
215+ if (! isFloating.value) return ;
216+ floatingManager.disposeFloating (_floatTag);
217+ isFloating.value = false ;
218+ }
219+
133220 Future <void > setVolume (double volume) async {
134221 final clamped = volume.clamp (0.0 , 1.0 );
135222 currentVolume.value = clamped;
@@ -168,6 +255,7 @@ class SwitchableGlobalPlayer {
168255
169256 Widget getVideoWidget (Widget ? child) {
170257 return Obx (() {
258+ final bool isFloatContent = isFloating.value && child == null ;
171259 if (! isInitialized.value) {
172260 return Material (
173261 child: Stack (
@@ -200,7 +288,7 @@ class SwitchableGlobalPlayer {
200288 children: [
201289 Container (color: Colors .black),
202290 _currentPlayer? .getVideoWidget (settings.videoFitIndex.value, child) ?? const SizedBox (),
203- child ?? const SizedBox (),
291+ if ( ! isFloatContent) child ?? const SizedBox (),
204292 ],
205293 ),
206294 resizeToAvoidBottomInset: true ,
@@ -238,7 +326,7 @@ class SwitchableGlobalPlayer {
238326 children: [
239327 Container (color: Colors .black),
240328 _currentPlayer? .getVideoWidget (settings.videoFitIndex.value, child) ?? const SizedBox (),
241- child ?? const SizedBox (),
329+ if ( ! isFloatContent) child ?? const SizedBox (),
242330 ],
243331 ),
244332 resizeToAvoidBottomInset: true ,
0 commit comments