Skip to content

Commit 5933083

Browse files
committed
fix: swift 6 concurrency
1 parent 49ff1be commit 5933083

11 files changed

Lines changed: 87 additions & 94 deletions

Front Row.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@
378378
STRING_CATALOG_GENERATE_SYMBOLS = YES;
379379
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
380380
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
381-
SWIFT_VERSION = 5.0;
381+
SWIFT_VERSION = 6.0;
382382
};
383383
name = Debug;
384384
};
@@ -438,7 +438,7 @@
438438
SDKROOT = macosx;
439439
STRING_CATALOG_GENERATE_SYMBOLS = YES;
440440
SWIFT_COMPILATION_MODE = wholemodule;
441-
SWIFT_VERSION = 5.0;
441+
SWIFT_VERSION = 6.0;
442442
};
443443
name = Release;
444444
};

Front Row/FrontRowApp.swift

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,13 @@ import SwiftUI
1111
@main
1212
struct FrontRowApp: App {
1313
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
14-
@State private var playEngine: PlayEngine
15-
@State private var presentedViewManager: PresentedViewManager
16-
@State private var windowController: WindowController
14+
@State private var playEngine = PlayEngine.shared
15+
@State private var presentedViewManager = PresentedViewManager.shared
16+
@State private var windowController = WindowController.shared
1717
private let updaterController: SPUStandardUpdaterController
1818
private let keyDownListener = KeyDownListener()
1919

2020
init() {
21-
self._playEngine = .init(wrappedValue: .shared)
22-
self._presentedViewManager = .init(wrappedValue: .shared)
23-
self._windowController = .init(wrappedValue: .shared)
24-
2521
updaterController = SPUStandardUpdaterController(
2622
startingUpdater: true,
2723
updaterDelegate: nil,
@@ -37,7 +33,6 @@ struct FrontRowApp: App {
3733
Window("Front Row", id: "main") {
3834
ContentView()
3935
.preferredColorScheme(.dark)
40-
.environment(playEngine)
4136
.sheet(isPresented: $presentedViewManager.isPresentingOpenURLView) {
4237
OpenURLView()
4338
.frame(minWidth: 600)
@@ -70,18 +65,15 @@ struct FrontRowApp: App {
7065
}
7166
.windowStyle(.hiddenTitleBar)
7267
.restorationBehavior(.disabled)
68+
.environment(playEngine)
69+
.environment(presentedViewManager)
70+
.environment(windowController)
7371
.commands {
7472
AppCommands(updater: updaterController.updater)
75-
FileCommands(playEngine: $playEngine)
76-
ViewCommands(
77-
playEngine: $playEngine,
78-
windowController: $windowController)
79-
PlaybackCommands(
80-
playEngine: $playEngine,
81-
presentedViewManager: $presentedViewManager)
82-
WindowCommands(
83-
playEngine: $playEngine,
84-
windowController: $windowController)
73+
FileCommands()
74+
ViewCommands()
75+
PlaybackCommands()
76+
WindowCommands()
8577
HelpCommands()
8678
}
8779
}

Front Row/Main Menu/FileCommands.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import AVKit
99
import SwiftUI
1010

1111
struct FileCommands: Commands {
12-
@Binding var playEngine: PlayEngine
12+
@Environment(PlayEngine.self) private var playEngine
13+
@Environment(PresentedViewManager.self) private var viewManager
1314

1415
var body: some Commands {
1516
CommandGroup(replacing: .newItem) {
@@ -26,7 +27,7 @@ struct FileCommands: Commands {
2627
.keyboardShortcut("O", modifiers: [.command])
2728

2829
Button {
29-
PresentedViewManager.shared.isPresentingOpenURLView.toggle()
30+
viewManager.isPresentingOpenURLView.toggle()
3031
} label: {
3132
Text(
3233
"Open URL...",
@@ -38,8 +39,9 @@ struct FileCommands: Commands {
3839
Divider()
3940

4041
Button {
41-
guard let item = PlayEngine.shared.player.currentItem else { return }
42-
guard let asset = item.asset as? AVURLAsset else { return }
42+
guard let item = playEngine.player.currentItem,
43+
let asset = item.asset as? AVURLAsset
44+
else { return }
4345
NSWorkspace.shared.activateFileViewerSelecting([asset.url])
4446
} label: {
4547
Text(
@@ -51,6 +53,7 @@ struct FileCommands: Commands {
5153
}
5254
}
5355

56+
@MainActor
5457
private func showOpenFileDialog() async {
5558
let panel = NSOpenPanel()
5659
panel.allowedContentTypes = PlayEngine.supportedFileTypes
@@ -63,7 +66,7 @@ struct FileCommands: Commands {
6366
}
6467

6568
guard let url = panel.url else { return }
66-
guard await PlayEngine.shared.openFile(url: url) else { return }
69+
guard await playEngine.openFile(url: url) else { return }
6770
NSDocumentController.shared.noteNewRecentDocumentURL(url)
6871
}
6972
}

Front Row/Main Menu/PlaybackCommands.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import AVKit
99
import SwiftUI
1010

1111
struct PlaybackCommands: Commands {
12-
@Binding var playEngine: PlayEngine
13-
@Binding var presentedViewManager: PresentedViewManager
12+
@Environment(PlayEngine.self) private var playEngine
13+
@Environment(PresentedViewManager.self) private var presentedViewManager
1414

1515
var body: some Commands {
16+
@Bindable var playEngine = playEngine
17+
@Bindable var presentedViewManager = presentedViewManager
18+
1619
CommandMenu("Playback") {
1720
Button {
1821
playEngine.playPause()
@@ -138,7 +141,7 @@ struct PlaybackCommands: Commands {
138141

139142
Divider()
140143

141-
audioTrackPicker
144+
audioTrackPicker(playEngine: $playEngine)
142145

143146
Toggle(isOn: $playEngine.isMuted) {
144147
Text("Mute")
@@ -147,9 +150,9 @@ struct PlaybackCommands: Commands {
147150
}
148151
}
149152

150-
@ViewBuilder private var audioTrackPicker: some View {
151-
if let group = playEngine.audioGroup {
152-
Picker("Audio Track", selection: $playEngine.audioTrack) {
153+
@ViewBuilder private func audioTrackPicker(playEngine: Bindable<PlayEngine>) -> some View {
154+
if let group = playEngine.wrappedValue.audioGroup {
155+
Picker("Audio Track", selection: playEngine.audioTrack) {
153156
Text("Off").tag(nil as AVMediaSelectionOption?)
154157
ForEach(group.options) { option in
155158
Text(verbatim: option.displayName).tag(Optional(option))

Front Row/Main Menu/ViewCommands.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import AVKit
99
import SwiftUI
1010

1111
struct ViewCommands: Commands {
12-
@Binding var playEngine: PlayEngine
13-
@Binding var windowController: WindowController
12+
@Environment(PlayEngine.self) private var playEngine
13+
@Environment(WindowController.self) private var windowController
1414

1515
var body: some Commands {
16+
@Bindable var playEngine = playEngine
17+
@Bindable var windowController = windowController
18+
1619
CommandGroup(replacing: .toolbar) {
1720
Button {
1821
NSApplication.shared.mainWindow?.toggleFullScreen(nil)
@@ -27,13 +30,13 @@ struct ViewCommands: Commands {
2730

2831
Divider()
2932

30-
subtitlePicker
33+
subtitlePicker(playEngine: $playEngine)
3134
}
3235
}
3336

34-
@ViewBuilder private var subtitlePicker: some View {
35-
if let group = playEngine.subtitleGroup {
36-
Picker("Subtitle", selection: $playEngine.subtitle) {
37+
@ViewBuilder private func subtitlePicker(playEngine: Bindable<PlayEngine>) -> some View {
38+
if let group = playEngine.wrappedValue.subtitleGroup {
39+
Picker("Subtitle", selection: playEngine.subtitle) {
3740
Text("Off").tag(nil as AVMediaSelectionOption?)
3841

3942
let optionsWithoutForcedSubs = group.options.filter {

Front Row/Main Menu/WindowCommands.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
import SwiftUI
99

1010
struct WindowCommands: Commands {
11-
@Binding var playEngine: PlayEngine
12-
@Binding var windowController: WindowController
11+
@Environment(PlayEngine.self) private var playEngine
12+
@Environment(WindowController.self) private var windowController
1313

1414
var body: some Commands {
1515
CommandGroup(after: .windowSize) {
1616
Section {
1717
Button {
18-
PlayEngine.shared.fitToVideoSize()
18+
playEngine.fitToVideoSize()
1919
} label: {
2020
Text(
2121
"Natural Size",

Front Row/Support/NowPlayable+RemoteCommands.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import MediaPlayer
99

1010
extension NowPlayable {
11-
func setupRemoteCommandHandlers(playEngine: PlayEngine) {
11+
@MainActor func setupRemoteCommandHandlers(playEngine: PlayEngine) {
1212
let commandCenter = MPRemoteCommandCenter.shared()
1313

1414
commandCenter.playCommand.isEnabled = true

Front Row/Support/PlayEngine.swift

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AVKit
99
import Combine
1010
import SwiftUI
1111

12+
@MainActor
1213
@Observable public final class PlayEngine {
1314

1415
static let shared = PlayEngine()
@@ -145,7 +146,7 @@ import SwiftUI
145146

146147
private var timeObserver: Any?
147148

148-
init() {
149+
private init() {
149150
NowPlayable.shared.sessionStart()
150151
NowPlayable.shared.setupRemoteCommandHandlers(playEngine: self)
151152

@@ -154,24 +155,24 @@ import SwiftUI
154155

155156
player.publisher(for: \.timeControlStatus)
156157
.receive(on: DispatchQueue.main)
157-
.sink { status in
158-
self.timeControlStatus = status
159-
self.updateNowPlayingInfo()
158+
.sink { [weak self] status in
159+
self?.timeControlStatus = status
160+
self?.updateNowPlayingInfo()
160161
}
161162
.store(in: &subs)
162163

163164
player.publisher(for: \.rate)
164165
.receive(on: DispatchQueue.main)
165-
.sink { rate in
166-
self.updateNowPlayingInfo()
166+
.sink { [weak self] rate in
167+
self?.updateNowPlayingInfo()
167168
}
168169
.store(in: &subs)
169170

170171
player.publisher(for: \.isMuted)
171172
.removeDuplicates()
172173
.receive(on: DispatchQueue.main)
173-
.sink { isMuted in
174-
self._isMuted = isMuted
174+
.sink { [weak self] isMuted in
175+
self?._isMuted = isMuted
175176
}
176177
.store(in: &subs)
177178

@@ -189,35 +190,27 @@ import SwiftUI
189190
/// Attempts to open file at url. If its not playable, returns false.
190191
/// - Parameter url: A URL to a local, remote, or HTTP Live Streaming media resource.
191192
/// - Returns: A Boolean value that indicates whether an asset contains playable content.
192-
@MainActor
193193
@discardableResult func openFile(url: URL) async -> Bool {
194194
if asset != nil {
195195
asset!.cancelLoading()
196196
}
197-
asset = AVURLAsset(url: url)
197+
let newAsset = AVURLAsset(url: url)
198+
asset = newAsset
199+
198200
do {
199-
let isPlayable = try await asset!.load(.isPlayable)
201+
let isPlayable = try await newAsset.load(.isPlayable)
200202
guard isPlayable else { return false }
201203

202-
if let subtitleGroup = try await asset!.loadMediaSelectionGroup(for: .legible) {
203-
self.subtitleGroup = subtitleGroup
204-
} else {
205-
self.subtitleGroup = nil
206-
}
207-
208-
if let audioGroup = try await asset!.loadMediaSelectionGroup(for: .audible) {
209-
self.audioGroup = audioGroup
210-
} else {
211-
self.audioGroup = nil
212-
}
204+
self.subtitleGroup = try? await newAsset.loadMediaSelectionGroup(for: .legible)
205+
self.audioGroup = try? await newAsset.loadMediaSelectionGroup(for: .audible)
213206
} catch {
214207
return false
215208
}
216209

217210
for sub in currentItemSubs { sub.cancel() }
218211
currentItemSubs.removeAll()
219212

220-
let playerItem = AVPlayerItem(asset: asset!)
213+
let playerItem = AVPlayerItem(asset: newAsset)
221214

222215
playerItem.publisher(for: \.status)
223216
.removeDuplicates()
@@ -226,17 +219,18 @@ import SwiftUI
226219
guard let self else { return }
227220
switch status {
228221
case .readyToPlay:
229-
isLoaded = true
230-
isLocalFile = FileManager.default.fileExists(
231-
atPath: url.path(percentEncoded: false))
222+
self.isLoaded = true
223+
self.isLocalFile = FileManager.default.fileExists(atPath: url.path)
232224
NowPlayable.shared.setNowPlayingMetadata(
233225
NowPlayableStaticMetadata(
234-
assetURL: url, mediaType: videoSize == CGSize.zero ? .audio : .video,
235-
title: url.lastPathComponent))
236-
updateNowPlayingInfo()
226+
assetURL: url,
227+
mediaType: self.videoSize == .zero ? .audio : .video,
228+
title: url.lastPathComponent
229+
))
230+
self.updateNowPlayingInfo()
237231
case .failed:
238-
isLoaded = false
239-
isLocalFile = false
232+
self.isLoaded = false
233+
self.isLocalFile = false
240234
NowPlayable.shared.sessionEnd()
241235
default:
242236
break
@@ -249,25 +243,16 @@ import SwiftUI
249243
.receive(on: DispatchQueue.main)
250244
.sink { [weak self] size in
251245
guard let self else { return }
252-
videoSize = size
253-
fitToVideoSize(skipResize: WindowController.shared.isFullscreen)
246+
self.videoSize = size
247+
self.fitToVideoSize(skipResize: WindowController.shared.isFullscreen)
254248
}
255249
.store(in: &currentItemSubs)
256250

257251
player.replaceCurrentItem(with: playerItem)
258252
player.play()
259253

260-
if let subtitleGroup {
261-
subtitle = subtitleGroup.options.first
262-
} else {
263-
subtitle = nil
264-
}
265-
266-
if let audioGroup {
267-
audioTrack = audioGroup.options.first
268-
} else {
269-
audioTrack = nil
270-
}
254+
self.subtitle = subtitleGroup?.options.first
255+
self.audioTrack = audioGroup?.options.first
271256

272257
return true
273258
}
@@ -420,17 +405,20 @@ import SwiftUI
420405

421406
private func addPeriodicTimeObserver() {
422407
let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
408+
423409
timeObserver = player.addPeriodicTimeObserver(
424410
forInterval: interval,
425411
queue: .main
426412
) { [weak self] time in
427-
guard let self else { return }
428-
_currentTime = time.seconds
413+
Task { @MainActor in
414+
guard let self else { return }
415+
self._currentTime = time.seconds
429416

430-
guard let duration = player.currentItem?.duration.seconds else { return }
431-
guard !duration.isNaN && !duration.isInfinite else { return }
432-
self.duration = duration
433-
timeRemaining = duration - _currentTime
417+
guard let duration = self.player.currentItem?.duration.seconds else { return }
418+
guard !duration.isNaN && !duration.isInfinite else { return }
419+
self.duration = duration
420+
self.timeRemaining = duration - self._currentTime
421+
}
434422
}
435423
}
436424

0 commit comments

Comments
 (0)