@neoskola/auto-play 0.3.16 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ios/Types.swift
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import CarPlay
|
|
9
9
|
|
|
10
|
+
private struct NowPlayingPushResult {
|
|
11
|
+
let success: Bool
|
|
12
|
+
let errorDescription: String?
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
@MainActor
|
|
11
16
|
class AutoPlayInterfaceController: NSObject, CPInterfaceControllerDelegate {
|
|
12
17
|
let interfaceController: CPInterfaceController
|
|
@@ -61,7 +66,28 @@ class AutoPlayInterfaceController: NSObject, CPInterfaceControllerDelegate {
|
|
|
61
66
|
return true
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
let firstAttempt = await pushNowPlayingTemplateSafely(animated: animated)
|
|
70
|
+
if firstAttempt.success || interfaceController.topTemplate is CPNowPlayingTemplate {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// CarPlay can reject pushes while handling list selection transitions.
|
|
75
|
+
// Retry once after a short delay to avoid transient failures.
|
|
76
|
+
try? await Task.sleep(nanoseconds: 250_000_000)
|
|
77
|
+
let secondAttempt = await pushNowPlayingTemplateSafely(animated: animated)
|
|
78
|
+
if secondAttempt.success || interfaceController.topTemplate is CPNowPlayingTemplate {
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let reason = secondAttempt.errorDescription
|
|
83
|
+
?? firstAttempt.errorDescription
|
|
84
|
+
?? "unknown reason"
|
|
85
|
+
let topTemplateDescription = interfaceController.topTemplate.map {
|
|
86
|
+
String(describing: type(of: $0))
|
|
87
|
+
} ?? "nil"
|
|
88
|
+
throw AutoPlayError.pushFailed(
|
|
89
|
+
"CPNowPlayingTemplate push failed (\(reason)). top=\(topTemplateDescription) stackCount=\(interfaceController.templates.count)"
|
|
90
|
+
)
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
return try await interfaceController.pushTemplate(
|
|
@@ -71,19 +97,36 @@ class AutoPlayInterfaceController: NSObject, CPInterfaceControllerDelegate {
|
|
|
71
97
|
}
|
|
72
98
|
|
|
73
99
|
/// Pushes CPNowPlayingTemplate using ObjC @try/@catch to prevent crash from NSException.
|
|
74
|
-
///
|
|
75
|
-
private func pushNowPlayingTemplateSafely(animated: Bool) ->
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
/// Uses a completion handler to confirm the push actually succeeded.
|
|
101
|
+
private func pushNowPlayingTemplateSafely(animated: Bool) async -> NowPlayingPushResult {
|
|
102
|
+
return await withCheckedContinuation { continuation in
|
|
103
|
+
do {
|
|
104
|
+
try ObjCExceptionCatcher.push(
|
|
105
|
+
CPNowPlayingTemplate.shared,
|
|
106
|
+
on: self.interfaceController,
|
|
107
|
+
animated: animated,
|
|
108
|
+
completion: { success, error in
|
|
109
|
+
if let error = error {
|
|
110
|
+
print("[AutoPlay] CPNowPlayingTemplate push completion error: \(error.localizedDescription)")
|
|
111
|
+
}
|
|
112
|
+
print("[AutoPlay] CPNowPlayingTemplate push completion: success=\(success)")
|
|
113
|
+
continuation.resume(
|
|
114
|
+
returning: NowPlayingPushResult(
|
|
115
|
+
success: success,
|
|
116
|
+
errorDescription: error?.localizedDescription
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
} catch {
|
|
122
|
+
print("[AutoPlay] CPNowPlayingTemplate push threw ObjC exception: \(error.localizedDescription)")
|
|
123
|
+
continuation.resume(
|
|
124
|
+
returning: NowPlayingPushResult(
|
|
125
|
+
success: false,
|
|
126
|
+
errorDescription: error.localizedDescription
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
87
130
|
}
|
|
88
131
|
}
|
|
89
132
|
|
|
@@ -43,6 +43,12 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
|
|
|
43
43
|
NowPlayingSessionManager.shared.ensureSessionActive()
|
|
44
44
|
setupRemoteCommandCenter()
|
|
45
45
|
updateNowPlayingInfo()
|
|
46
|
+
|
|
47
|
+
// Set playbackState before push so CarPlay sees an active now-playing session
|
|
48
|
+
if config.isPlaying {
|
|
49
|
+
MPNowPlayingInfoCenter.default().playbackState = .playing
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
isSetupComplete = true
|
|
47
53
|
|
|
48
54
|
if let image = config.image {
|
|
@@ -301,10 +307,13 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
|
|
|
301
307
|
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
|
302
308
|
}
|
|
303
309
|
|
|
310
|
+
// Keep playback metadata populated even before duration is known.
|
|
311
|
+
// This helps CarPlay recognize an active now-playing session.
|
|
312
|
+
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
|
|
313
|
+
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
|
|
314
|
+
|
|
304
315
|
if currentDuration > 0 {
|
|
305
316
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentDuration
|
|
306
|
-
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
|
|
307
|
-
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
|
|
308
317
|
}
|
|
309
318
|
|
|
310
319
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
|
@@ -13,4 +13,10 @@ onInterfaceController:(CPInterfaceController * _Nonnull)interfaceController
|
|
|
13
13
|
animated:(BOOL)animated
|
|
14
14
|
error:(NSError * _Nullable * _Nullable)error;
|
|
15
15
|
|
|
16
|
+
+ (BOOL)pushTemplate:(CPTemplate * _Nonnull)templateToPush
|
|
17
|
+
onInterfaceController:(CPInterfaceController * _Nonnull)interfaceController
|
|
18
|
+
animated:(BOOL)animated
|
|
19
|
+
completion:(void (^ _Nullable)(BOOL success, NSError * _Nullable error))completion
|
|
20
|
+
error:(NSError * _Nullable * _Nullable)error;
|
|
21
|
+
|
|
16
22
|
@end
|
|
@@ -23,4 +23,26 @@ onInterfaceController:(CPInterfaceController *)interfaceController
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
+ (BOOL)pushTemplate:(CPTemplate *)templateToPush
|
|
27
|
+
onInterfaceController:(CPInterfaceController *)interfaceController
|
|
28
|
+
animated:(BOOL)animated
|
|
29
|
+
completion:(void (^)(BOOL, NSError *))completion
|
|
30
|
+
error:(NSError **)error {
|
|
31
|
+
@try {
|
|
32
|
+
[interfaceController pushTemplate:templateToPush animated:animated completion:completion];
|
|
33
|
+
return YES;
|
|
34
|
+
} @catch (NSException *exception) {
|
|
35
|
+
NSLog(@"[ObjCExceptionCatcher] Caught exception during pushTemplate: %@ — %@", exception.name, exception.reason);
|
|
36
|
+
if (error) {
|
|
37
|
+
*error = [NSError errorWithDomain:@"ObjCExceptionCatcher"
|
|
38
|
+
code:0
|
|
39
|
+
userInfo:@{
|
|
40
|
+
NSLocalizedDescriptionKey: exception.reason ?: @"Unknown Objective-C exception",
|
|
41
|
+
@"ExceptionName": exception.name ?: @"Unknown"
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
return NO;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
@end
|