-
-
Save codeactual/6a03cde03cd5867b8f3f3395f2208566 to your computer and use it in GitHub Desktop.
SwiftUI: Rewrite iOS Photos Video Scrubber
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| import AVKit | |
| struct VideoPlayerControls: View { | |
| let player: AVPlayer | |
| @Binding var currentTime: CGFloat | |
| var height: CGFloat = 50 | |
| var actionImage: String = "plus" | |
| @State private var isPlaying: Bool = false | |
| @State private var isTracking: Bool = false | |
| @State private var timeObserver: Any? | |
| var action: (() -> Void)? | |
| var body: some View { | |
| HStack(spacing: 0) { | |
| Button { | |
| isPlaying ? player.pause() : player.play() | |
| isPlaying.toggle() | |
| } label: { | |
| Image(systemName: isPlaying ? "pause.fill" : "play.fill") | |
| .resizable() | |
| .padding() | |
| .frame(width: height, height: height, alignment: .center) | |
| } | |
| .foregroundColor(.white) | |
| .overlay(Rectangle().frame(width: 1, height: nil).foregroundColor(Color.black), alignment: .trailing) | |
| VideoScrollPreview(player: player, isPlaying: $isPlaying, currentTime: $currentTime, isTracking: $isTracking) | |
| .padding(4) | |
| .frame(width: nil, height: height) | |
| if let action = action { | |
| Button { | |
| action() | |
| } label: { | |
| Image(systemName: actionImage) | |
| .resizable() | |
| .padding() | |
| .frame(width: height, height: height, alignment: .center) | |
| } | |
| .foregroundColor(.white) | |
| .overlay(Rectangle().frame(width: 1, height: nil).foregroundColor(Color.black), alignment: .leading) | |
| } | |
| } | |
| .background(Color.darkGray) | |
| .cornerRadius(5) | |
| .onAppear { | |
| startPeriodicTimeObserver() | |
| } | |
| .onDisappear { | |
| stopPeriodicTimeObserver() | |
| } | |
| } | |
| func startPeriodicTimeObserver() { | |
| timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: nil) { time in | |
| guard isTracking == false else { return } | |
| guard let duration = VideoHelper.getDuration(player) else { return } | |
| self.currentTime = CGFloat(CMTimeGetSeconds(time) / CMTimeGetSeconds(duration)) | |
| if self.currentTime == 1.0 { | |
| self.isPlaying = false | |
| } | |
| } | |
| } | |
| func stopPeriodicTimeObserver() { | |
| guard let observer = timeObserver else { return } | |
| player.removeTimeObserver(observer) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| import AVKit | |
| struct VideoScrollPreview: View { | |
| let player: AVPlayer | |
| @Binding var isPlaying: Bool | |
| @Binding var currentTime: CGFloat | |
| @Binding var isTracking: Bool | |
| @State private var images: [UIImage] = [] | |
| var body: some View { | |
| GeometryReader { geometry in | |
| ZStack { | |
| HStack(spacing: 0) { | |
| ForEach(images, id: \.self) { image in | |
| Image(uiImage: image) | |
| .resizable() | |
| .scaledToFit() | |
| } | |
| } | |
| RoundedRectangle(cornerRadius: 10, style: .continuous) | |
| .frame(width: 4, height: geometry.size.height + 4) | |
| .position(x: currentTime * geometry.size.width, y: geometry.size.height / 2) | |
| .foregroundColor(.white) | |
| .shadow(radius: 10) | |
| } | |
| .gesture( | |
| DragGesture(minimumDistance: 0) | |
| .onChanged({ | |
| isTracking = true | |
| if isPlaying { | |
| player.pause() | |
| } | |
| currentTime = min(geometry.size.width, max(0, $0.location.x)) / geometry.size.width | |
| guard let duration = VideoHelper.getDuration(player) else { return } | |
| let targetTime = CMTimeMultiplyByFloat64(duration, multiplier: Float64(currentTime)) | |
| player.seek(to: targetTime) | |
| }) | |
| .onEnded({ _ in | |
| isTracking = false | |
| if isPlaying { | |
| player.play() | |
| } | |
| }) | |
| ) | |
| .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY) | |
| .onAppear { | |
| images = VideoHelper.generateThumbnailImages(player, geometry.size) | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment