functional carplay experience in simulators
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'streaming_audio_business_object.dart';
|
||||
|
||||
class CarPlayBridge {
|
||||
CarPlayBridge({required StreamingAudioBusinessObject audio}) : _audio = audio;
|
||||
|
||||
static const MethodChannel _channel = MethodChannel(
|
||||
'kryz_go_flutter/carplay/methods',
|
||||
);
|
||||
|
||||
final StreamingAudioBusinessObject _audio;
|
||||
bool _isStarted = false;
|
||||
|
||||
Future<void> start() async {
|
||||
if (_isStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isStarted = true;
|
||||
_channel.setMethodCallHandler(_handleNativeMethodCall);
|
||||
_audio.addListener(_onAudioChanged);
|
||||
await _pushSnapshotToNative();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
if (!_isStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isStarted = false;
|
||||
_audio.removeListener(_onAudioChanged);
|
||||
_channel.setMethodCallHandler(null);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _snapshot() {
|
||||
final String playbackStatus = switch (_audio.playbackStatus) {
|
||||
StreamingPlaybackStatus.playing => 'playing',
|
||||
StreamingPlaybackStatus.buffering => 'buffering',
|
||||
StreamingPlaybackStatus.stopped => 'stopped',
|
||||
};
|
||||
|
||||
return <String, dynamic>{
|
||||
'playbackStatus': playbackStatus,
|
||||
'isPlaying': _audio.isPlaying,
|
||||
'isBuffering': _audio.isBuffering,
|
||||
'isLive': true,
|
||||
'seekable': false,
|
||||
'title': _audio.currentTitle,
|
||||
'subtitle': _audio.currentSubtitle,
|
||||
'album': 'Live',
|
||||
};
|
||||
}
|
||||
|
||||
Future<dynamic> _handleNativeMethodCall(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case 'getStateSnapshot':
|
||||
return _snapshot();
|
||||
default:
|
||||
throw MissingPluginException('Unknown CarPlay bridge method: ${call.method}');
|
||||
}
|
||||
}
|
||||
|
||||
void _onAudioChanged() {
|
||||
_pushSnapshotToNative();
|
||||
}
|
||||
|
||||
Future<void> _pushSnapshotToNative() async {
|
||||
try {
|
||||
await _channel.invokeMethod<void>('syncState', _snapshot());
|
||||
} on MissingPluginException {
|
||||
// CarPlay bridge only exists on iOS native builds.
|
||||
} on PlatformException {
|
||||
// Keep playback working even if native sync temporarily fails.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,13 @@ class KryzAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
// CarPlay and lock-screen transport may issue pause for live streams.
|
||||
// Treat pause as stop to keep behavior consistent across platforms.
|
||||
await stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seek(Duration position) async {
|
||||
// Live stream is intentionally non-seekable.
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
import 'app_theme_business_object.dart';
|
||||
import 'carplay_bridge.dart';
|
||||
import 'casting_business_object.dart';
|
||||
import 'live_info_business_object.dart';
|
||||
import 'live_info_panel.dart';
|
||||
@@ -35,16 +36,20 @@ class MyApp extends StatefulWidget {
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final StreamingAudioBusinessObject _audio = StreamingAudioBusinessObject();
|
||||
final CastingBusinessObject _casting = CastingBusinessObject();
|
||||
late final CarPlayBridge _carPlayBridge;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_carPlayBridge = CarPlayBridge(audio: _audio);
|
||||
unawaited(_audio.ensureInitialized());
|
||||
unawaited(_carPlayBridge.start());
|
||||
_casting.initialize();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_carPlayBridge.dispose());
|
||||
_casting.dispose();
|
||||
_audio.dispose();
|
||||
super.dispose();
|
||||
|
||||
Reference in New Issue
Block a user