-
-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: replace VideoPlayer dependency with UIKit+SwiftUI combinati…
…on (#57) * Update Package.resolved * Update MockMessages.swift * feat: activate picture in picture background mode * refactor!: drop iOS 14 and macOS 11 support * refactor: remove VideoPlayer dependency * chore: update file syntax * fix: correct several issues * Update README.md * Update swift.yml * chore: add changelog * Update MockMessages.swift
- Loading branch information
1 parent
15ed0ee
commit ef58abf
Showing
18 changed files
with
394 additions
and
307 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import AVFoundation | ||
import Combine | ||
|
||
final class PlayerViewModel: ObservableObject { | ||
let player = AVPlayer() | ||
@Published var isInPipMode: Bool = false | ||
@Published var isPlaying = false | ||
|
||
@Published var isEditingCurrentTime = false | ||
@Published var currentTime: Double = .zero | ||
@Published var duration: Double? | ||
|
||
private var subscriptions: Set<AnyCancellable> = [] | ||
private var timeObserver: Any? | ||
|
||
deinit { | ||
if let timeObserver = timeObserver { | ||
player.removeTimeObserver(timeObserver) | ||
} | ||
} | ||
|
||
init() { | ||
$isEditingCurrentTime | ||
.dropFirst() | ||
.filter({ $0 == false }) | ||
.sink(receiveValue: { [weak self] _ in | ||
guard let self else { return } | ||
|
||
self.player.seek( | ||
to: CMTime(seconds: self.currentTime, preferredTimescale: 1), | ||
toleranceBefore: .zero, | ||
toleranceAfter: .zero | ||
) | ||
|
||
if self.player.rate != 0 { | ||
self.player.play() | ||
} | ||
}) | ||
.store(in: &subscriptions) | ||
|
||
player.publisher(for: \.timeControlStatus) | ||
.sink { [weak self] status in | ||
switch status { | ||
case .playing: | ||
self?.isPlaying = true | ||
case .paused: | ||
self?.isPlaying = false | ||
case .waitingToPlayAtSpecifiedRate: | ||
break | ||
@unknown default: | ||
break | ||
} | ||
} | ||
.store(in: &subscriptions) | ||
|
||
timeObserver = player.addPeriodicTimeObserver( | ||
forInterval: CMTime(seconds: 1, preferredTimescale: 600), | ||
queue: .main | ||
) { [weak self] time in | ||
guard let self else { return } | ||
|
||
if self.isEditingCurrentTime == false { | ||
self.currentTime = time.seconds | ||
} | ||
} | ||
} | ||
|
||
func setCurrentItem(_ item: AVPlayerItem) { | ||
currentTime = .zero | ||
duration = nil | ||
player.replaceCurrentItem(with: item) | ||
|
||
item.publisher(for: \.status) | ||
.filter({ $0 == .readyToPlay }) | ||
.sink(receiveValue: { [weak self] _ in | ||
self?.duration = item.asset.duration.seconds | ||
}) | ||
.store(in: &subscriptions) | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
Sources/SwiftyChat/Common/Video/SwiftUIViews/CustomControlsView.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import SwiftUI | ||
|
||
struct CustomControlsView<Message: ChatMessage>: View { | ||
@ObservedObject var playerVM: PlayerViewModel | ||
@EnvironmentObject var videoManager: VideoManager<Message> | ||
|
||
init(for playerViewModel: PlayerViewModel) { | ||
self.playerVM = playerViewModel | ||
} | ||
|
||
var body: some View { | ||
HStack { | ||
playPauseButton | ||
durationSlider | ||
fullScreenButton | ||
closeButton | ||
} | ||
.imageScale(.large) | ||
.padding() | ||
.background(.thinMaterial) | ||
} | ||
|
||
private var playPauseButton: some View { | ||
Color.secondary.colorInvert() | ||
.cornerRadius(10) | ||
.frame(width: 50, height: 40) | ||
.overlay( | ||
Image(systemName: playerVM.isPlaying ? "pause.fill" : "play.fill") | ||
.font(Font.body.weight(.semibold)) | ||
.foregroundColor(Color.white) | ||
.padding() | ||
) | ||
.onTapGesture { | ||
if playerVM.isPlaying { | ||
playerVM.player.pause() | ||
} else { | ||
playerVM.player.play() | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private var durationSlider: some View { | ||
if let duration = playerVM.duration { | ||
Slider( | ||
value: $playerVM.currentTime, | ||
in: 0...duration, | ||
onEditingChanged: { isEditing in | ||
playerVM.isEditingCurrentTime = isEditing | ||
} | ||
) | ||
} else { | ||
Spacer() | ||
} | ||
} | ||
|
||
private var fullScreenButton: some View { | ||
Color.secondary.colorInvert() | ||
.cornerRadius(10) | ||
.frame(width: 50, height: 40) | ||
.overlay( | ||
Image( | ||
systemName: videoManager.isFullScreen ? | ||
"arrow.down.right.and.arrow.up.left" : | ||
"arrow.up.left.and.arrow.down.right" | ||
) | ||
.font(Font.body.weight(.semibold)) | ||
.foregroundColor(Color.white) | ||
.padding() | ||
) | ||
.onTapGesture { | ||
withAnimation { | ||
videoManager.isFullScreen.toggle() | ||
} | ||
} | ||
} | ||
|
||
private var closeButton: some View { | ||
Color.secondary.colorInvert() | ||
.cornerRadius(10) | ||
.frame(width: 50, height: 40) | ||
.overlay( | ||
Image(systemName: "xmark") | ||
.font(Font.body.weight(.semibold)) | ||
.foregroundColor(Color.white) | ||
.padding() | ||
) | ||
.onTapGesture { | ||
self.videoManager.flushState() | ||
} | ||
} | ||
} |
Oops, something went wrong.