chore: auto-pause when audio cell disappeared

This commit is contained in:
sunxiaojian 2021-03-11 13:11:13 +08:00
parent 2657dde184
commit 6c0a767435
10 changed files with 56 additions and 41 deletions

View File

@ -25,7 +25,7 @@
2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; };
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7F25F5F45E00143C56 /* UIImage.swift */; };
2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; };
2D206B8C25F6015000143C56 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8B25F6015000143C56 /* AudioPlayer.swift */; };
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */; };
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; };
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; };
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; };
@ -269,7 +269,7 @@
2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = "<group>"; };
2D206B7F25F5F45E00143C56 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
2D206B8B25F6015000143C56 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlaybackService.swift; sourceTree = "<group>"; };
2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
@ -659,7 +659,7 @@
DB45FB0425CA87B4005A8AC7 /* APIService */,
DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */,
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
2D206B8B25F6015000143C56 /* AudioPlayer.swift */,
2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */,
2DA6054625F716A2006356F9 /* PlaybackState.swift */,
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */,
);
@ -1569,7 +1569,7 @@
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */,
5DF1057F25F88A4100D6C0D4 /* TouchBlockingView.swift in Sources */,
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */,
2D206B8C25F6015000143C56 /* AudioPlayer.swift in Sources */,
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */,
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */,
2D7631B325C159F700929FB9 /* Item.swift in Sources */,

View File

@ -176,7 +176,7 @@ extension StatusSection {
// set audio
if let audioAttachment = mediaAttachments.filter({ $0.type == .audio }).first {
cell.statusView.audioView.isHidden = false
AudioContainerViewModel.configure(cell: cell, audioAttachment: audioAttachment )
AudioContainerViewModel.configure(cell: cell, audioAttachment: audioAttachment, audioService: dependency.context.audioPlaybackService)
} else {
cell.statusView.audioView.isHidden = true
}

View File

@ -87,10 +87,14 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
.sink { [weak self] toot in
guard let self = self else { return }
guard let media = (toot?.mediaAttachments ?? Set()).first else { return }
guard let videoPlayerViewModel = self.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: media) else { return }
DispatchQueue.main.async {
videoPlayerViewModel.didEndDisplaying()
if let videoPlayerViewModel = self.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: media) {
DispatchQueue.main.async {
videoPlayerViewModel.didEndDisplaying()
}
}
if let currentAudioAttachment = self.context.audioPlaybackService.attachment, let _ = toot?.mediaAttachments?.contains(currentAudioAttachment) {
self.context.audioPlaybackService.pause()
}
}
.store(in: &disposeBag)

View File

@ -142,6 +142,7 @@ extension HomeTimelineViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
context.videoPlaybackService.viewDidDisappear(from: self)
context.audioPlaybackService.viewDidDisappear(from: self)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

View File

@ -84,6 +84,7 @@ extension PublicTimelineViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
context.videoPlaybackService.viewDidDisappear(from: self)
context.audioPlaybackService.viewDidDisappear(from: self)
}
}
@ -107,7 +108,6 @@ extension PublicTimelineViewController {
// MARK: - UITableViewDelegate
extension PublicTimelineViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
guard let diffableDataSource = viewModel.diffableDataSource else { return 100 }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return 100 }

View File

@ -12,54 +12,58 @@ import UIKit
class AudioContainerViewModel {
static func configure(
cell: StatusTableViewCell,
audioAttachment: Attachment
audioAttachment: Attachment,
audioService: AudioPlaybackService
) {
guard let duration = audioAttachment.meta?.original?.duration else { return }
let audioView = cell.statusView.audioView
audioView.timeLabel.text = duration.asString(style: .positional)
audioView.playButton.publisher(for: .touchUpInside)
.sink { _ in
if audioAttachment === AudioPlayer.shared.attachment {
if AudioPlayer.shared.isPlaying() {
AudioPlayer.shared.pause()
.sink { [weak audioService] _ in
guard let audioService = audioService else { return }
if audioAttachment === audioService.attachment {
if audioService.isPlaying() {
audioService.pause()
} else {
AudioPlayer.shared.resume()
audioService.resume()
}
if AudioPlayer.shared.currentTimeSubject.value == 0 {
AudioPlayer.shared.playAudio(audioAttachment: audioAttachment)
if audioService.currentTimeSubject.value == 0 {
audioService.playAudio(audioAttachment: audioAttachment)
}
} else {
AudioPlayer.shared.playAudio(audioAttachment: audioAttachment)
audioService.playAudio(audioAttachment: audioAttachment)
}
}
.store(in: &cell.disposeBag)
audioView.slider.publisher(for: .valueChanged)
.sink { slider in
.sink { [weak audioService] slider in
guard let audioService = audioService else { return }
let slider = slider as! UISlider
let time = Double(slider.value) * duration
AudioPlayer.shared.seekToTime(time: time)
audioService.seekToTime(time: time)
}
.store(in: &cell.disposeBag)
observePlayer(cell: cell, audioAttachment: audioAttachment)
if audioAttachment != AudioPlayer.shared.attachment {
observePlayer(cell: cell, audioAttachment: audioAttachment, audioService: audioService)
if audioAttachment != audioService.attachment {
configureAudioView(audioView: audioView, audioAttachment: audioAttachment, playbackState: .stopped)
}
}
static func observePlayer(
cell: StatusTableViewCell,
audioAttachment: Attachment
audioAttachment: Attachment,
audioService: AudioPlaybackService
) {
let audioView = cell.statusView.audioView
var lastCurrentTimeSubject: TimeInterval?
AudioPlayer.shared.currentTimeSubject
audioService.currentTimeSubject
.throttle(for: 0.33, scheduler: DispatchQueue.main, latest: true)
.compactMap { time -> (TimeInterval, Float)? in
.compactMap { [weak audioService] time -> (TimeInterval, Float)? in
defer {
lastCurrentTimeSubject = time
}
guard audioAttachment === AudioPlayer.shared.attachment else { return nil }
guard audioAttachment === audioService?.attachment else { return nil }
guard let duration = audioAttachment.meta?.original?.duration else { return nil }
if let lastCurrentTimeSubject = lastCurrentTimeSubject, time != 0.0 {
@ -74,10 +78,10 @@ class AudioContainerViewModel {
audioView.slider.setValue(progress, animated: true)
})
.store(in: &cell.disposeBag)
AudioPlayer.shared.playbackState
audioService.playbackState
.receive(on: DispatchQueue.main)
.sink(receiveValue: { playbackState in
if audioAttachment === AudioPlayer.shared.attachment {
if audioAttachment === audioService.attachment {
configureAudioView(audioView: audioView, audioAttachment: audioAttachment, playbackState: playbackState)
} else {
configureAudioView(audioView: audioView, audioAttachment: audioAttachment, playbackState: .stopped)

View File

@ -14,7 +14,7 @@ import UIKit
final class VideoPlayerViewModel {
var disposeBag = Set<AnyCancellable>()
static let appWillPlayVideoNotification = NSNotification.Name(rawValue: "appWillPlayVideoNotification")
static let appWillPlayVideoNotification = NSNotification.Name(rawValue: "org.joinmastodon.Mastodon.VideoPlayerViewModel.appWillPlayVideo")
// input
let previewImageURL: URL?
let videoURL: URL

View File

@ -10,10 +10,11 @@ import Combine
import CoreDataStack
import Foundation
import UIKit
import os.log
final class AudioPlayer: NSObject {
final class AudioPlaybackService: NSObject {
static let appWillPlayAudioNotification = NSNotification.Name(rawValue: "appWillPlayAudioNotification")
static let appWillPlayAudioNotification = NSNotification.Name(rawValue: "org.joinmastodon.Mastodon.AudioPlayer.appWillPlayAudio")
var disposeBag = Set<AnyCancellable>()
@ -24,19 +25,16 @@ final class AudioPlayer: NSObject {
let session = AVAudioSession.sharedInstance()
let playbackState = CurrentValueSubject<PlaybackState, Never>(PlaybackState.unknown)
// MARK: - singleton
public static let shared = AudioPlayer()
let currentTimeSubject = CurrentValueSubject<TimeInterval, Never>(0)
private override init() {
override init() {
super.init()
addObserver()
}
}
extension AudioPlayer {
extension AudioPlaybackService {
func playAudio(audioAttachment: Attachment) {
guard let url = URL(string: audioAttachment.url) else {
return
@ -48,7 +46,7 @@ extension AudioPlayer {
return
}
pushWillPlayAudioNotification()
notifyWillPlayAudioNotification()
if audioAttachment == attachment {
if self.playbackState.value == .stopped {
self.seekToTime(time: .zero)
@ -129,14 +127,14 @@ extension AudioPlayer {
.store(in: &disposeBag)
}
func pushWillPlayAudioNotification() {
NotificationCenter.default.post(name: AudioPlayer.appWillPlayAudioNotification, object: nil)
func notifyWillPlayAudioNotification() {
NotificationCenter.default.post(name: AudioPlaybackService.appWillPlayAudioNotification, object: nil)
}
func isPlaying() -> Bool {
return playbackState.value == .readyToPlay || playbackState.value == .playing
}
func resume() {
pushWillPlayAudioNotification()
notifyWillPlayAudioNotification()
player.play()
playbackState.value = .playing
}
@ -154,3 +152,10 @@ extension AudioPlayer {
player.seek(to: CMTimeMake(value:Int64(time), timescale: 1))
}
}
extension AudioPlaybackService {
func viewDidDisappear(from viewController: UIViewController?) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
pause()
}
}

View File

@ -91,7 +91,7 @@ extension VideoPlaybackService {
}
.store(in: &disposeBag)
NotificationCenter.default.publisher(for: AudioPlayer.appWillPlayAudioNotification)
NotificationCenter.default.publisher(for: AudioPlaybackService.appWillPlayAudioNotification)
.sink { [weak self] _ in
guard let self = self else { return }
self.pauseWhenPlayAudio()

View File

@ -28,6 +28,7 @@ class AppContext: ObservableObject {
private var documentStoreSubscription: AnyCancellable!
let videoPlaybackService = VideoPlaybackService()
let audioPlaybackService = AudioPlaybackService()
let overrideTraitCollection = CurrentValueSubject<UITraitCollection?, Never>(nil)