Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:flutter/services.dart';
4 : import 'package:flutter_chrome_cast/_remote_media_client/remote_media_client_platform.dart';
5 : import 'package:flutter_chrome_cast/entities/cast_media_status.dart';
6 : import 'package:flutter_chrome_cast/entities/load_options.dart';
7 : import 'package:flutter_chrome_cast/entities/media_seek_option.dart';
8 : import 'package:flutter_chrome_cast/entities/queue_item.dart';
9 : import 'package:flutter_chrome_cast/entities/media_information.dart';
10 : import 'package:flutter_chrome_cast/models/ios/ios_cast_queue_item.dart';
11 : import 'package:flutter_chrome_cast/models/ios/ios_media_status.dart';
12 : import 'package:rxdart/rxdart.dart';
13 :
14 : /// iOS-specific implementation of Google Cast remote media client functionality.
15 : class GoogleCastRemoteMediaClientIOSMethodChannel
16 : implements GoogleCastRemoteMediaClientPlatformInterface {
17 : /// Creates a new iOS remote media client method channel.
18 1 : GoogleCastRemoteMediaClientIOSMethodChannel() {
19 3 : _channel.setMethodCallHandler(_methodCallHandler);
20 : }
21 :
22 : final _channel = const MethodChannel('google_cast.remote_media_client');
23 : bool _queueHasNextItem = false;
24 :
25 : final _mediaStatusStreamController = BehaviorSubject<GoggleCastMediaStatus?>()
26 : ..add(null);
27 :
28 : final _playerPositionStreamController = BehaviorSubject<Duration>()
29 : ..add(Duration.zero);
30 :
31 : final _queueItemsStreamController =
32 : BehaviorSubject<List<GoogleCastQueueItem>>()..add([]);
33 :
34 1 : @override
35 2 : GoggleCastMediaStatus? get mediaStatus => _mediaStatusStreamController.value;
36 :
37 1 : @override
38 : Stream<GoggleCastMediaStatus?> get mediaStatusStream =>
39 2 : _mediaStatusStreamController.stream;
40 :
41 1 : @override
42 2 : Duration get playerPosition => _playerPositionStreamController.value;
43 :
44 1 : @override
45 : Stream<Duration> get playerPositionStream =>
46 2 : _playerPositionStreamController.stream;
47 :
48 1 : @override
49 2 : List<GoogleCastQueueItem> get queueItems => _queueItemsStreamController.value;
50 :
51 1 : @override
52 : Stream<List<GoogleCastQueueItem>> get queueItemsStream =>
53 2 : _queueItemsStreamController.stream;
54 :
55 1 : @override
56 1 : bool get queueHasNextItem => _queueHasNextItem;
57 :
58 1 : @override
59 : bool get queueHasPreviousItem {
60 1 : final index = queueItems
61 1 : .map((e) => e.itemId)
62 1 : .toList()
63 2 : .lastIndexOf(mediaStatus?.currentItemId);
64 1 : return index > 0;
65 : }
66 :
67 1 : @override
68 : Future<void> loadMedia(
69 : GoogleCastMediaInformation mediaInfo, {
70 : bool autoPlay = true,
71 : Duration playPosition = Duration.zero,
72 : double playbackRate = 1.0,
73 : List<int>? activeTrackIds,
74 : String? credentials,
75 : String? credentialsType,
76 : }) async {
77 2 : _channel.invokeMethod(
78 : 'loadMedia',
79 1 : mediaInfo.toMap()
80 1 : ..addAll(
81 1 : {
82 : 'autoPlay': autoPlay,
83 1 : 'playPosition': playPosition.inSeconds,
84 : 'playbackRate': playbackRate,
85 : 'activeTrackIds': activeTrackIds,
86 : 'credentials': credentials,
87 : 'credentialsType': credentialsType,
88 2 : }..removeWhere((key, value) => value == null),
89 : ));
90 : }
91 :
92 1 : @override
93 : Future<void> pause() async {
94 2 : await _channel.invokeMethod('pause');
95 : }
96 :
97 1 : @override
98 : Future<void> play() async {
99 2 : await _channel.invokeMethod('play');
100 : }
101 :
102 1 : @override
103 : Future<void> setActiveTrackIDs(List<int> activeTrackIDs) async {
104 3 : await _channel.invokeMethod('setActiveTrackIDs', activeTrackIDs.toList());
105 : }
106 :
107 1 : @override
108 : Future<void> setPlaybackRate(double rate) async {
109 2 : await _channel.invokeMethod('setPlaybackRate', rate);
110 : }
111 :
112 1 : @override
113 : Future<void> setTextTrackStyle(TextTrackStyle textTrackStyle) async {
114 3 : await _channel.invokeMethod('setTextTrackStyle', textTrackStyle.toMap());
115 : }
116 :
117 1 : @override
118 : Future<void> stop() async {
119 2 : await _channel.invokeMethod('stop');
120 : }
121 :
122 1 : @override
123 : Future<void> seek(GoogleCastMediaSeekOption option) async {
124 3 : await _channel.invokeMethod('seek', option.toMap());
125 : }
126 :
127 1 : @override
128 : Future<void> queueNextItem() async {
129 2 : await _channel.invokeMethod('queueNextItem');
130 : }
131 :
132 1 : @override
133 : Future<void> queuePrevItem() async {
134 2 : _channel.invokeMethod('queuePrevItem');
135 : }
136 :
137 : // MARK: - MethodCallHandler
138 0 : Future _methodCallHandler(MethodCall call) async {
139 0 : switch (call.method) {
140 0 : case "onUpdateMediaStatus":
141 0 : return await _onUpdateMediaStatus(call.arguments);
142 0 : case "onUpdatePlayerPosition":
143 0 : return await _onUpdatePlayerPosition(call.arguments);
144 0 : case "updateQueueItems":
145 0 : return await _updateQueueItems(call.arguments);
146 : default:
147 : }
148 : }
149 :
150 0 : Future _onUpdatePlayerPosition(int seconds) async {
151 0 : final duration = Duration(seconds: seconds);
152 0 : _playerPositionStreamController.add(duration);
153 : }
154 :
155 0 : FutureOr<void> _onUpdateMediaStatus(dynamic arguments) {
156 : if (arguments != null) {
157 : try {
158 0 : arguments = Map<String, dynamic>.from(arguments);
159 0 : final mediaStatus = GoogleCastIOSMediaStatus.fromMap(arguments);
160 0 : _queueHasNextItem = arguments["queueHasNextItem"];
161 0 : _mediaStatusStreamController.add(mediaStatus);
162 : } catch (e) {
163 : rethrow;
164 : }
165 : }
166 : }
167 :
168 1 : @override
169 : Future<void> queueLoadItems(
170 : List<GoogleCastQueueItem> queueItems, {
171 : GoogleCastQueueLoadOptions? options,
172 : }) async {
173 2 : _channel.invokeMethod(
174 : 'queueLoadItems',
175 1 : {
176 5 : 'items': queueItems.map((item) => item.toMap()).toList(),
177 2 : if (options != null) 'options': options.toMap(),
178 : },
179 : );
180 : }
181 :
182 1 : @override
183 : Future<void> queueInsertItems(List<GoogleCastQueueItem> items,
184 : {int? beforeItemWithId}) async {
185 3 : _channel.invokeMethod('queueInsertItems', {
186 4 : 'items': items.map((item) => item.toMap()).toList(),
187 : 'beforeItemWithId': beforeItemWithId,
188 : });
189 : }
190 :
191 1 : @override
192 : Future<void> queueInsertItemAndPlay(
193 : GoogleCastQueueItem item, {
194 : required int beforeItemWithId,
195 : }) async {
196 2 : assert(beforeItemWithId >= 0, 'beforeItemWithId must be greater than 0');
197 3 : _channel.invokeMethod('queueInsertItemAndPlay', {
198 1 : 'item': item.toMap(),
199 : 'beforeItemWithId': beforeItemWithId,
200 : });
201 : return;
202 : }
203 :
204 1 : @override
205 : Future<void> queueJumpToItemWithId(int itemId) async {
206 2 : _channel.invokeMethod('queueJumpToItemWithId', itemId);
207 : }
208 :
209 1 : @override
210 : Future<void> queueRemoveItemsWithIds(List<int> itemIds) async {
211 2 : _channel.invokeMethod('queueRemoveItemsWithIds', itemIds);
212 : }
213 :
214 0 : FutureOr<void> _updateQueueItems(dynamic arguments) async {
215 0 : final items = List.from(arguments ?? []);
216 : final queueItems = items
217 0 : .map((item) =>
218 0 : GoogleCastQueueItemIOS.fromMap(Map<String, dynamic>.from(item)))
219 0 : .toList();
220 0 : _queueItemsStreamController.add(queueItems);
221 : }
222 :
223 1 : @override
224 : Future<void> queueReorderItems(
225 : {required List<int> itemsIds, required int? beforeItemWithId}) async {
226 2 : _channel.invokeMethod(
227 : 'queueReorderItems',
228 1 : {
229 : 'itemsIds': itemsIds,
230 : 'beforeItemWithId': beforeItemWithId,
231 : },
232 : );
233 : }
234 : }
|