Skip to content

Commit

Permalink
Merge pull request #236 from SDWebImage/bugfix_webimage_no_stop_playi…
Browse files Browse the repository at this point in the history
…ng_quick_scroll

Fix the case which sometimes the player does not stop when WebImage it out of screen
  • Loading branch information
dreampiggy authored Sep 23, 2022
2 parents 53ac75c + 657c64a commit c60b04b
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 40 deletions.
19 changes: 14 additions & 5 deletions SDWebImageSwiftUI/Classes/ImagePlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public final class ImagePlayer : ObservableObject {

deinit {
player?.stopPlaying()
currentFrame = nil
}

/// Current playing frame image
Expand All @@ -57,7 +56,7 @@ public final class ImagePlayer : ObservableObject {
if let player = player {
return player.isPlaying && waitingPlaying
}
return false
return true
}

/// Current playing status
Expand Down Expand Up @@ -106,11 +105,21 @@ public final class ImagePlayer : ObservableObject {
currentAnimatedImage = animatedImage
if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) {
imagePlayer.animationFrameHandler = { [weak self] (index, frame) in
self?.currentFrameIndex = index
self?.currentFrame = frame
guard let self = self else {
return
}
if (self.isPlaying) {
self.currentFrameIndex = index
self.currentFrame = frame
}
}
imagePlayer.animationLoopHandler = { [weak self] (loopCount) in
self?.currentLoopCount = loopCount
guard let self = self else {
return
}
if (self.isPlaying) {
self.currentLoopCount = loopCount
}
}
// Setup configuration
if let maxBufferSize = maxBufferSize {
Expand Down
77 changes: 49 additions & 28 deletions SDWebImageSwiftUI/Classes/WebImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,17 @@ public struct WebImage : View {
}

public var body: some View {
return Group {
// Render Logic
// Container
return ZStack {
// This empty Image is used to receive container's level appear/disappear to start/stop player, reduce CPU usage
Image(platformImage: .empty)
.onAppear {
self.appearAction()
}
.onDisappear {
self.disappearAction()
}
// Render Logic for actual animated image frame or static image
if imageManager.image != nil && imageModel.url == imageManager.currentURL {
if isAnimating && !imageManager.isIncremental {
setupPlayer()
Expand All @@ -118,7 +127,7 @@ public struct WebImage : View {
// Load Logic
setupPlaceholder()
.onPlatformAppear(appear: {
setupManager()
self.setupManager()
if (self.imageManager.error == nil) {
// Load remote image when first appear
self.imageManager.load(url: imageModel.url, options: imageModel.options, context: imageModel.context)
Expand Down Expand Up @@ -205,36 +214,50 @@ public struct WebImage : View {
}
}

/// Container level to resume animation when appear
func appearAction() {
self.imagePlayer.startPlaying()
}

/// Container level to stop animation when disappear
func disappearAction() {
if self.imageConfiguration.pausable {
self.imagePlayer.pausePlaying()
} else {
self.imagePlayer.stopPlaying()
}
if self.imageConfiguration.purgeable {
self.imagePlayer.clearFrameBuffer()
}
}

/// Animated Image Support
func setupPlayer() -> some View {
let disappearAction = {
// Only stop the player which is not intermediate status
if !imagePlayer.isWaiting {
if self.imageConfiguration.pausable {
self.imagePlayer.pausePlaying()
} else {
self.imagePlayer.stopPlaying()
}
if self.imageConfiguration.purgeable {
self.imagePlayer.clearFrameBuffer()
}
}
let shouldResetPlayer: Bool
// Image compare should use ===/!==, which is faster than isEqual:
if let animatedImage = imagePlayer.currentAnimatedImage, animatedImage !== imageManager.image! {
shouldResetPlayer = true
} else {
shouldResetPlayer = false
}
if let currentFrame = imagePlayer.currentFrame, imagePlayer.currentAnimatedImage == imageManager.image! {
return configure(image: currentFrame).onPlatformAppear(appear: {
self.imagePlayer.startPlaying()
}, disappear: {
disappearAction()
})
if let currentFrame = imagePlayer.currentFrame, !shouldResetPlayer {
// Bind frame index to ID to ensure onDisappear called with sync
return configure(image: currentFrame)
.id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)")
.onAppear {}
} else {
return configure(image: imageManager.image!).onPlatformAppear(appear: {
self.imagePlayer.stopPlaying()
if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider {
return configure(image: imageManager.image!)
.id("\(imageModel.url!):\(imagePlayer.currentFrameIndex)")
.onAppear {
if shouldResetPlayer {
// Clear previous status
self.imagePlayer.player = nil;
self.imagePlayer.stopPlaying()
self.imagePlayer.player = nil
self.imagePlayer.currentFrame = nil;
self.imagePlayer.currentFrameIndex = 0;
self.imagePlayer.currentLoopCount = 0;
}
if let animatedImage = imageManager.image as? PlatformImage & SDAnimatedImageProvider {
self.imagePlayer.customLoopCount = self.imageConfiguration.customLoopCount
self.imagePlayer.maxBufferSize = self.imageConfiguration.maxBufferSize
self.imagePlayer.runLoopMode = self.imageConfiguration.runLoopMode
Expand All @@ -244,9 +267,7 @@ public struct WebImage : View {
self.imagePlayer.setupPlayer(animatedImage: animatedImage)
self.imagePlayer.startPlaying()
}
}, disappear: {
disappearAction()
})
}
}
}

Expand Down
14 changes: 7 additions & 7 deletions Tests/WebImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class WebImageTests: XCTestCase {
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, data, cacheType in
#if os(macOS)
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
#else
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
#endif
XCTAssertNotNil(displayImage)
expectation.fulfill()
Expand All @@ -47,10 +47,10 @@ class WebImageTests: XCTestCase {
if let animatedImage = image as? SDAnimatedImage {
XCTAssertTrue(imageView.isAnimating)
#if os(macOS)
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
let size = displayImage?.size
#else
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
let size = CGSize(width: displayImage?.width ?? 0, height: displayImage?.height ?? 0)
#endif
XCTAssertNotNil(displayImage)
Expand Down Expand Up @@ -161,11 +161,11 @@ class WebImageTests: XCTestCase {
let imageView = WebImage(url: imageUrl)
let introspectView = imageView.onSuccess { image, data, cacheType in
#if os(macOS)
let displayImage = try? imageView.inspect().group().image(0).actualImage().nsImage()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().nsImage()
XCTAssertNotNil(displayImage)
#else
let displayImage = try? imageView.inspect().group().image(0).actualImage().cgImage()
let orientation = try? imageView.inspect().group().image(0).actualImage().orientation()
let displayImage = try? imageView.inspect().zStack().image(1).actualImage().cgImage()
let orientation = try? imageView.inspect().zStack().image(1).actualImage().orientation()
XCTAssertNotNil(displayImage)
XCTAssertEqual(orientation, .leftMirrored)
#endif
Expand Down

0 comments on commit c60b04b

Please sign in to comment.