Skip to content

Commit

Permalink
fix(crash): Fix crash on attaching metadata output when loading items…
Browse files Browse the repository at this point in the history
… too quickly (#68)
  • Loading branch information
dcvz authored Oct 20, 2023
1 parent 0c87d24 commit c03c830
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 8 deletions.
18 changes: 14 additions & 4 deletions Sources/SwiftAudioEx/Observer/AVPlayerItemObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,32 @@ class AVPlayerItemObserver: NSObject {
*/
func startObserving(item: AVPlayerItem) {
stopObservingCurrentItem()
isObserving = true
observingItem = item

self.isObserving = true
self.observingItem = item
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, options: [.new], context: &AVPlayerItemObserver.context)
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, options: [.new], context: &AVPlayerItemObserver.context)
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.playbackLikelyToKeepUp, options: [.new], context: &AVPlayerItemObserver.context)
item.add(metadataOutput)

// We must slightly delay adding the metadata output due to the fact that
// stop observation is not a synchronous action and metadataOutput may not
// be removed from last item before we try to attach it to a new one.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [weak self] in
guard let `self` = self else { return }
item.add(self.metadataOutput)
}
}

func stopObservingCurrentItem() {
guard let observingItem = observingItem, isObserving else {
return
}

observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, context: &AVPlayerItemObserver.context)
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, context: &AVPlayerItemObserver.context)
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.playbackLikelyToKeepUp, context: &AVPlayerItemObserver.context)
observingItem.remove(metadataOutput)

isObserving = false
self.observingItem = nil
}
Expand Down
7 changes: 7 additions & 0 deletions Tests/SwiftAudioExTests/AVPlayerItemObserverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ class AVPlayerItemObserverTests: XCTestCase {
observer.startObserving(item: item)
XCTAssertTrue(observer.isObserving)
}

func testObservingInQuickSucccession() {
for _ in 0...1000 {
let item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
observer.startObserving(item: item)
}
}
}
4 changes: 0 additions & 4 deletions Tests/SwiftAudioExTests/AudioPlayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ class AudioPlayerTests: XCTestCase {
}

func testLoadResourceSucceedsAfterPreviousFailure() {
let expectation = XCTestExpectation(description: "Load resource succeeds after previous failure")

var didReceiveFail = false
listener.onReceiveFail = { error in
didReceiveFail = true
Expand All @@ -230,8 +228,6 @@ class AudioPlayerTests: XCTestCase {
}

func testLoadResourceSucceedsAfterPreviousFailureWithPlayWhenReady() {
let expectation = XCTestExpectation(description: "Load resource with playWhenReady succeeds after previous failure")

var didReceiveFail = false
listener.onReceiveFail = { error in
didReceiveFail = true
Expand Down

0 comments on commit c03c830

Please sign in to comment.