Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,163 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import '../../../shared/utils/app_logger.dart';
const _tag = 'AndroidPip';
enum PipAction { play, pause, next, prev, seekForward, seekBackward }
class AndroidPipController {
static const _methodChannel = MethodChannel('smplayer/pip');
static const _eventChannel = EventChannel('smplayer/pip_events');
AndroidPipController._();
static final AndroidPipController instance = AndroidPipController._();
StreamController<bool>? _pipModeController;
StreamController<PipAction>? _actionController;
StreamSubscription<dynamic>? _eventSub;
Future<bool> isSupported() async {
if (!Platform.isAndroid) return false;
try {
return (await _methodChannel.invokeMethod<bool>('isSupported')) ?? false;
} catch (e) {
AppLogger.warn(_tag, 'isSupported check failed', e);
return false;
}
}
Future<bool> isInPipMode() async {
if (!Platform.isAndroid) return false;
try {
return (await _methodChannel.invokeMethod<bool>('isInPipMode')) ?? false;
} catch (e) {
return false;
}
}
Future<void> configure({
int aspectX = 16,
int aspectY = 9,
bool autoEnterOnLeave = false,
bool isPlaying = false,
bool hasNext = false,
bool hasPrev = false,
bool useEpisodeActions = true,
}) async {
if (!Platform.isAndroid) return;
try {
await _methodChannel.invokeMethod<bool>('configure', {
'aspectX': aspectX,
'aspectY': aspectY,
'autoEnterOnLeave': autoEnterOnLeave,
'isPlaying': isPlaying,
'hasNext': hasNext,
'hasPrev': hasPrev,
'useEpisodeActions': useEpisodeActions,
});
} catch (e) {
AppLogger.warn(_tag, 'configure failed', e);
}
}
Future<bool> enterPip({int? aspectX, int? aspectY}) async {
if (!Platform.isAndroid) return false;
try {
return (await _methodChannel.invokeMethod<bool>('enterPip', {
'aspectX': aspectX ?? 16,
'aspectY': aspectY ?? 9,
})) ??
false;
} catch (e) {
AppLogger.warn(_tag, 'enterPip failed', e);
return false;
}
}
Future<void> updatePlaybackState({
required bool isPlaying,
required bool hasNext,
required bool hasPrev,
required bool useEpisodeActions,
}) async {
if (!Platform.isAndroid) return;
try {
await _methodChannel.invokeMethod<bool>('updatePlaybackState', {
'isPlaying': isPlaying,
'hasNext': hasNext,
'hasPrev': hasPrev,
'useEpisodeActions': useEpisodeActions,
});
} catch (e) {
AppLogger.warn(_tag, 'updatePlaybackState failed', e);
}
}
Stream<bool> get pipModeChanges {
_ensureEventListening();
return (_pipModeController ??= StreamController<bool>.broadcast()).stream;
}
Stream<PipAction> get actionStream {
_ensureEventListening();
return (_actionController ??= StreamController<PipAction>.broadcast())
.stream;
}
void _ensureEventListening() {
if (_eventSub != null) return;
_eventSub = _eventChannel.receiveBroadcastStream().listen(
(event) {
if (event is! Map) return;
final map = event.cast<Object?, Object?>();
final type = map['type'] as String?;
if (type == 'modeChanged') {
final isInPip = map['isInPip'] as bool? ?? false;
_pipModeController?.add(isInPip);
} else if (type == 'action') {
final action = _parseAction(map['action'] as String?);
if (action != null) {
_actionController?.add(action);
}
}
},
onError: (Object e) {
AppLogger.warn(_tag, 'event stream error', e);
},
);
}
Future<void> dispose() async {
final eventSub = _eventSub;
_eventSub = null;
await eventSub?.cancel();
await _pipModeController?.close();
_pipModeController = null;
await _actionController?.close();
_actionController = null;
}
PipAction? _parseAction(String? name) => switch (name) {
'play' => PipAction.play,
'pause' => PipAction.pause,
'next' => PipAction.next,
'prev' => PipAction.prev,
'seekForward' => PipAction.seekForward,
'seekBackward' => PipAction.seekBackward,
_ => null,
};
}