Initial commit
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user