@@ -19,9 +19,11 @@ final class BoyDemoViewModel: ObservableObject {
1919 @Published var lastError : String ?
2020
2121 private var session : Session ?
22- private var announcedGames : [ String : BroadcastInfo ] = [ : ]
22+ private var subscription : BroadcastSubscription ?
23+ private var announcedGames : [ String : Catalog ] = [ : ]
2324 private var stateObserverTask : Task < Void , Never > ?
2425 private var broadcastObserverTask : Task < Void , Never > ?
26+ private var catalogObserverTasks : [ String : Task < Void , Never > ] = [ : ]
2527 private var commandPublisher : Publisher ?
2628 private var commandEmitter : DataTrackEmitter ?
2729 private var viewerPath : String ?
@@ -109,22 +111,19 @@ final class BoyDemoViewModel: ObservableObject {
109111 }
110112 }
111113
112- broadcastObserverTask = Task { [ weak self] in
113- guard let self else { return }
114- for await event in session. broadcasts {
115- switch event {
116- case . available( let info) :
117- await self . handleAvailableBroadcast ( info)
118- case . unavailable( let path) :
119- await self . handleUnavailableBroadcast ( path)
120- }
121- }
122- }
123-
124114 Task { [ weak self] in
125115 do {
126116 try await session. connect ( )
127- try session. subscribe ( prefix: Self . subscribePrefix)
117+ let subscription = try await session. subscribe ( prefix: Self . subscribePrefix)
118+ await MainActor . run {
119+ self ? . subscription = subscription
120+ self ? . broadcastObserverTask = Task { [ weak self] in
121+ guard let self else { return }
122+ for await broadcast in subscription. broadcasts {
123+ self . observeCatalogs ( for: broadcast)
124+ }
125+ }
126+ }
128127 } catch {
129128 await MainActor . run {
130129 self ? . lastError = error. localizedDescription
@@ -139,6 +138,10 @@ final class BoyDemoViewModel: ObservableObject {
139138 stateObserverTask = nil
140139 broadcastObserverTask? . cancel ( )
141140 broadcastObserverTask = nil
141+ for (_, task) in catalogObserverTasks {
142+ task. cancel ( )
143+ }
144+ catalogObserverTasks. removeAll ( )
142145 repeatTask? . cancel ( )
143146 repeatTask = nil
144147 lastError = nil
@@ -149,10 +152,12 @@ final class BoyDemoViewModel: ObservableObject {
149152 currentEntry = nil
150153
151154 let session = session
155+ let subscription = subscription
152156 let viewerPath = viewerPath
153157 let publisher = commandPublisher
154158
155159 self . session = nil
160+ self . subscription = nil
156161 self . viewerPath = nil
157162 self . commandPublisher = nil
158163 self . commandEmitter = nil
@@ -163,11 +168,12 @@ final class BoyDemoViewModel: ObservableObject {
163168
164169 Task {
165170 if let viewerPath, let session {
166- session. unpublish ( path: viewerPath)
171+ await session. unpublish ( path: viewerPath)
167172 } else {
168173 publisher? . stop ( )
169174 }
170175 await entry? . stop ( )
176+ subscription? . cancel ( )
171177 await session? . close ( )
172178 }
173179 }
@@ -221,10 +227,25 @@ final class BoyDemoViewModel: ObservableObject {
221227 currentEntry? . updateTargetLatency ( ms: UInt64 ( steppedLatency) )
222228 }
223229
224- private func handleAvailableBroadcast( _ info: BroadcastInfo ) async {
225- guard let game = Self . makeGame ( from: info) else { return }
230+ private func observeCatalogs( for broadcast: Broadcast ) {
231+ catalogObserverTasks [ broadcast. path] ? . cancel ( )
232+ catalogObserverTasks [ broadcast. path] = Task { [ weak self] in
233+ guard let self else { return }
234+
235+ for await catalog in broadcast. catalogs ( ) {
236+ await self . handleAvailableBroadcast ( catalog)
237+ }
238+
239+ guard !Task. isCancelled else { return }
240+ await self . handleUnavailableBroadcast ( broadcast. path)
241+ self . catalogObserverTasks. removeValue ( forKey: broadcast. path)
242+ }
243+ }
244+
245+ private func handleAvailableBroadcast( _ catalog: Catalog ) async {
246+ guard let game = Self . makeGame ( from: catalog) else { return }
226247
227- announcedGames [ game. broadcastPath] = info
248+ announcedGames [ game. broadcastPath] = catalog
228249 rebuildGameList ( )
229250
230251 if selectedGamePath == game. broadcastPath, sessionState == . connected {
@@ -256,40 +277,44 @@ final class BoyDemoViewModel: ObservableObject {
256277 private func startSelectedGame( ) async {
257278 await stopCurrentPlayback ( )
258279
259- guard let selectedGamePath, let info = announcedGames [ selectedGamePath] else { return }
280+ guard let selectedGamePath, let catalog = announcedGames [ selectedGamePath] else { return }
260281
261- await replaceCurrentBroadcast ( with: info )
262- if let game = Self . makeGame ( from: info ) {
282+ await replaceCurrentBroadcast ( with: catalog )
283+ if let game = Self . makeGame ( from: catalog ) {
263284 await startCommandPublishing ( for: game)
264285 }
265286 }
266287
267- private func replaceCurrentBroadcast( with info : BroadcastInfo ) async {
288+ private func replaceCurrentBroadcast( with catalog : Catalog ) async {
268289 let previousEntry = currentEntry
269290 currentEntry = nil
270291 await previousEntry? . stop ( )
271292
272- let selectedTracks = preferredTracks ( for: info)
273- guard !selectedTracks. tracks. isEmpty else { return }
293+ let selectedTracks = preferredTracks ( for: catalog)
294+ guard selectedTracks. videoTrackName != nil || selectedTracks. audioTrackName != nil else {
295+ return
296+ }
274297
275298 let entry = BroadcastEntry (
276- info : info ,
277- initialVideoTrack : selectedTracks. videoTrack ,
299+ catalog : catalog ,
300+ initialVideoTrackName : selectedTracks. videoTrackName ,
278301 initialLatencyMs: UInt64 ( targetLatencyMs)
279302 )
280303 currentEntry = entry
281304
282305 do {
283306 let player = try Player (
284- tracks: selectedTracks. tracks,
307+ catalog: catalog,
308+ videoTrackName: selectedTracks. videoTrackName,
309+ audioTrackName: selectedTracks. audioTrackName,
285310 targetBufferingMs: UInt64 ( targetLatencyMs)
286311 )
287312 entry. attach ( player: player)
288313 try await player. play ( )
289314 } catch {
290315 entry. offline = true
291316 lastError =
292- " Unable to play \( Self . displayName ( for: info . path) ) : \( error. localizedDescription) "
317+ " Unable to play \( Self . displayName ( for: catalog . path) ) : \( error. localizedDescription) "
293318 }
294319 }
295320
@@ -300,7 +325,7 @@ final class BoyDemoViewModel: ObservableObject {
300325 repeatTask = nil
301326
302327 if let viewerPath, let session {
303- session. unpublish ( path: viewerPath)
328+ await session. unpublish ( path: viewerPath)
304329 } else {
305330 commandPublisher? . stop ( )
306331 }
@@ -325,7 +350,7 @@ final class BoyDemoViewModel: ObservableObject {
325350 let viewerId = Self . makeViewerId ( )
326351 let viewerPath = " \( Self . viewerPrefix) / \( game. viewerPathComponent) / \( viewerId) "
327352
328- try session. publish ( path: viewerPath, publisher: publisher)
353+ try await session. publish ( path: viewerPath, publisher: publisher)
329354 try await publisher. start ( )
330355
331356 self . commandEmitter = emitter
@@ -394,20 +419,11 @@ final class BoyDemoViewModel: ObservableObject {
394419 }
395420
396421 private func preferredTracks(
397- for info: BroadcastInfo
398- ) -> ( videoTrack: VideoTrackInfo ? , tracks: [ any TrackInfo ] ) {
399- let audioTrack = info. audioTracks. first
400- let highestVideoTrack = info. videoTracks. max ( by: isLowerQualityVideoTrack)
401-
402- var tracks : [ any TrackInfo ] = [ ]
403- if let highestVideoTrack {
404- tracks. append ( highestVideoTrack)
405- }
406- if let audioTrack {
407- tracks. append ( audioTrack)
408- }
409-
410- return ( highestVideoTrack, tracks)
422+ for catalog: Catalog
423+ ) -> ( videoTrackName: String ? , audioTrackName: String ? ) {
424+ let audioTrackName = catalog. audioTracks. first? . name
425+ let highestVideoTrackName = catalog. videoTracks. max ( by: isLowerQualityVideoTrack) ? . name
426+ return ( highestVideoTrackName, audioTrackName)
411427 }
412428
413429 private func isLowerQualityVideoTrack(
@@ -422,11 +438,11 @@ final class BoyDemoViewModel: ObservableObject {
422438 return UInt64 ( coded. width) * UInt64( coded. height)
423439 }
424440
425- private static func makeGame( from info : BroadcastInfo ) -> BoyGame ? {
426- let component = pathComponent ( from: info . path)
441+ private static func makeGame( from catalog : Catalog ) -> BoyGame ? {
442+ let component = pathComponent ( from: catalog . path)
427443 return BoyGame (
428444 name: component,
429- broadcastPath: info . path,
445+ broadcastPath: catalog . path,
430446 viewerPathComponent: component
431447 )
432448 }
0 commit comments