Fix the playback schedule issue

dev
wenlei 11 months ago
parent ec161c5c01
commit d51519e4a1

@ -20,6 +20,11 @@
770228F82B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228F52B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift */; }; 770228F82B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228F52B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift */; };
770228F92B5A224500E07F7A /* SliderControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228F62B5A224500E07F7A /* SliderControl.swift */; }; 770228F92B5A224500E07F7A /* SliderControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228F62B5A224500E07F7A /* SliderControl.swift */; };
770F6CA22B64F1DE0082F53A /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770F6CA12B64F1DE0082F53A /* Carousel.swift */; }; 770F6CA22B64F1DE0082F53A /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770F6CA12B64F1DE0082F53A /* Carousel.swift */; };
770FC3EF2B92C8130023DE28 /* Array+IndieMusic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770FC3EE2B92C8130023DE28 /* Array+IndieMusic.swift */; };
770FC3F52B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770FC3F12B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift */; };
770FC3F62B92F4380023DE28 /* AVPlayer+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770FC3F22B92F4380023DE28 /* AVPlayer+Rx.swift */; };
770FC3F72B92F4380023DE28 /* AVPlayerItem+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770FC3F32B92F4380023DE28 /* AVPlayerItem+Rx.swift */; };
770FC3F82B92F4380023DE28 /* AVPlayerLayer+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770FC3F42B92F4380023DE28 /* AVPlayerLayer+Rx.swift */; };
771233EC2B6B6476009FAF01 /* UIButton+IndieMusic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771233EB2B6B6476009FAF01 /* UIButton+IndieMusic.swift */; }; 771233EC2B6B6476009FAF01 /* UIButton+IndieMusic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771233EB2B6B6476009FAF01 /* UIButton+IndieMusic.swift */; };
771233EE2B6B8CE6009FAF01 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771233ED2B6B8CE6009FAF01 /* NavigationBar.swift */; }; 771233EE2B6B8CE6009FAF01 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771233ED2B6B8CE6009FAF01 /* NavigationBar.swift */; };
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77165D732B464493002AE0A5 /* BarButtonItem.swift */; }; 77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77165D732B464493002AE0A5 /* BarButtonItem.swift */; };
@ -283,6 +288,11 @@
770228F52B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Multiplier.swift"; sourceTree = "<group>"; }; 770228F52B5A224500E07F7A /* NSLayoutConstraint+Multiplier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Multiplier.swift"; sourceTree = "<group>"; };
770228F62B5A224500E07F7A /* SliderControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderControl.swift; sourceTree = "<group>"; }; 770228F62B5A224500E07F7A /* SliderControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderControl.swift; sourceTree = "<group>"; };
770F6CA12B64F1DE0082F53A /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = "<group>"; }; 770F6CA12B64F1DE0082F53A /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = "<group>"; };
770FC3EE2B92C8130023DE28 /* Array+IndieMusic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+IndieMusic.swift"; sourceTree = "<group>"; };
770FC3F12B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVAsynchronousKeyValueLoading+Rx.swift"; sourceTree = "<group>"; };
770FC3F22B92F4380023DE28 /* AVPlayer+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayer+Rx.swift"; sourceTree = "<group>"; };
770FC3F32B92F4380023DE28 /* AVPlayerItem+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayerItem+Rx.swift"; sourceTree = "<group>"; };
770FC3F42B92F4380023DE28 /* AVPlayerLayer+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayerLayer+Rx.swift"; sourceTree = "<group>"; };
771233EB2B6B6476009FAF01 /* UIButton+IndieMusic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+IndieMusic.swift"; sourceTree = "<group>"; }; 771233EB2B6B6476009FAF01 /* UIButton+IndieMusic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+IndieMusic.swift"; sourceTree = "<group>"; };
771233ED2B6B8CE6009FAF01 /* NavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; }; 771233ED2B6B8CE6009FAF01 /* NavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
77165D732B464493002AE0A5 /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = "<group>"; }; 77165D732B464493002AE0A5 /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = "<group>"; };
@ -577,6 +587,17 @@
path = Combine; path = Combine;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
770FC3F02B92F42D0023DE28 /* RxAVFoundation */ = {
isa = PBXGroup;
children = (
770FC3F12B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift */,
770FC3F22B92F4380023DE28 /* AVPlayer+Rx.swift */,
770FC3F32B92F4380023DE28 /* AVPlayerItem+Rx.swift */,
770FC3F42B92F4380023DE28 /* AVPlayerLayer+Rx.swift */,
);
path = RxAVFoundation;
sourceTree = "<group>";
};
7743999C2AFA18B0006F8EEA /* Player */ = { 7743999C2AFA18B0006F8EEA /* Player */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -821,6 +842,7 @@
77AC35742B6E3D8300D046C2 /* UIView+IndieMusic.swift */, 77AC35742B6E3D8300D046C2 /* UIView+IndieMusic.swift */,
7751D3852B45409000F1F2BD /* NSNotification+IndieMusic.swift */, 7751D3852B45409000F1F2BD /* NSNotification+IndieMusic.swift */,
7751009B2B62065800F46109 /* Date+IndieMusic.swift */, 7751009B2B62065800F46109 /* Date+IndieMusic.swift */,
770FC3EE2B92C8130023DE28 /* Array+IndieMusic.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -910,6 +932,7 @@
778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = { 778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
770FC3F02B92F42D0023DE28 /* RxAVFoundation */,
775D07562B5E47C4009270D3 /* ETPopupView */, 775D07562B5E47C4009270D3 /* ETPopupView */,
77A60D7A2B5B975600D4E435 /* ResourceLoader */, 77A60D7A2B5B975600D4E435 /* ResourceLoader */,
770228F22B5A224500E07F7A /* SliderControl */, 770228F22B5A224500E07F7A /* SliderControl */,
@ -1442,6 +1465,7 @@
77C9B9DF2B4BCE300006C83F /* Message.swift in Sources */, 77C9B9DF2B4BCE300006C83F /* Message.swift in Sources */,
77FA0B2C2B0C480B00404C5E /* AudioMoreActionViewModel.swift in Sources */, 77FA0B2C2B0C480B00404C5E /* AudioMoreActionViewModel.swift in Sources */,
778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */, 778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */,
770FC3F52B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift in Sources */,
77620D8D2B68844E00798861 /* Followers.swift in Sources */, 77620D8D2B68844E00798861 /* Followers.swift in Sources */,
77FA0B562B0F4ABF00404C5E /* MineSingleController.swift in Sources */, 77FA0B562B0F4ABF00404C5E /* MineSingleController.swift in Sources */,
778B8A9D2AF8ECFC0034AFD4 /* LibsManager.swift in Sources */, 778B8A9D2AF8ECFC0034AFD4 /* LibsManager.swift in Sources */,
@ -1504,6 +1528,7 @@
778B8A862AF8ECE50034AFD4 /* SearchViewModel.swift in Sources */, 778B8A862AF8ECE50034AFD4 /* SearchViewModel.swift in Sources */,
77C9B9DD2B4BC5D20006C83F /* FollowersViewModel.swift in Sources */, 77C9B9DD2B4BC5D20006C83F /* FollowersViewModel.swift in Sources */,
778B8A802AF8ECE50034AFD4 /* MineViewController.swift in Sources */, 778B8A802AF8ECE50034AFD4 /* MineViewController.swift in Sources */,
770FC3EF2B92C8130023DE28 /* Array+IndieMusic.swift in Sources */,
778B8A8F2AF8ECF20034AFD4 /* EmptyModel.swift in Sources */, 778B8A8F2AF8ECF20034AFD4 /* EmptyModel.swift in Sources */,
778B8ABD2AF8ED280034AFD4 /* View.swift in Sources */, 778B8ABD2AF8ED280034AFD4 /* View.swift in Sources */,
778B8A912AF8ECF20034AFD4 /* Album.swift in Sources */, 778B8A912AF8ECF20034AFD4 /* Album.swift in Sources */,
@ -1570,6 +1595,7 @@
7751D3762B43E8D200F1F2BD /* MusicStyle.swift in Sources */, 7751D3762B43E8D200F1F2BD /* MusicStyle.swift in Sources */,
77C9B9D72B4BBD780006C83F /* FollowingViewModel.swift in Sources */, 77C9B9D72B4BBD780006C83F /* FollowingViewModel.swift in Sources */,
7736FF442B4CECF2008D5DAD /* CommentViewModel.swift in Sources */, 7736FF442B4CECF2008D5DAD /* CommentViewModel.swift in Sources */,
770FC3F62B92F4380023DE28 /* AVPlayer+Rx.swift in Sources */,
77620D962B68E96C00798861 /* DateItem.swift in Sources */, 77620D962B68E96C00798861 /* DateItem.swift in Sources */,
778B8AA92AF8ED0E0034AFD4 /* UIImage+IndieMusic.swift in Sources */, 778B8AA92AF8ED0E0034AFD4 /* UIImage+IndieMusic.swift in Sources */,
77C9B9D12B4B99600006C83F /* BadgeButton.swift in Sources */, 77C9B9D12B4B99600006C83F /* BadgeButton.swift in Sources */,
@ -1596,6 +1622,7 @@
7751D37C2B4516BE00F1F2BD /* UILabel+IndieMusic.swift in Sources */, 7751D37C2B4516BE00F1F2BD /* UILabel+IndieMusic.swift in Sources */,
778B8AAC2AF8ED0E0034AFD4 /* UIViewController+Rx.swift in Sources */, 778B8AAC2AF8ED0E0034AFD4 /* UIViewController+Rx.swift in Sources */,
778B8A8D2AF8ECF20034AFD4 /* APIImage.swift in Sources */, 778B8A8D2AF8ECF20034AFD4 /* APIImage.swift in Sources */,
770FC3F72B92F4380023DE28 /* AVPlayerItem+Rx.swift in Sources */,
770228F92B5A224500E07F7A /* SliderControl.swift in Sources */, 770228F92B5A224500E07F7A /* SliderControl.swift in Sources */,
7751D36A2B42ED6C00F1F2BD /* TimingViewController.swift in Sources */, 7751D36A2B42ED6C00F1F2BD /* TimingViewController.swift in Sources */,
77FA0B462B0DFAFD00404C5E /* BindPhoneViewController.swift in Sources */, 77FA0B462B0DFAFD00404C5E /* BindPhoneViewController.swift in Sources */,
@ -1610,6 +1637,7 @@
77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */, 77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */,
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */, 77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */,
77CEFEFE2B82F18A0071B671 /* CommentToolView.swift in Sources */, 77CEFEFE2B82F18A0071B671 /* CommentToolView.swift in Sources */,
770FC3F82B92F4380023DE28 /* AVPlayerLayer+Rx.swift in Sources */,
7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */, 7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */,
77A60D822B5B97A100D4E435 /* AssetDataManager.swift in Sources */, 77A60D822B5B97A100D4E435 /* AssetDataManager.swift in Sources */,
77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */, 77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */,

@ -55,7 +55,7 @@ final class Application: NSObject {
if let currentTrackData = PINCache.shared.object(forKey: "CurrentTrack") as? Data, if let currentTrackData = PINCache.shared.object(forKey: "CurrentTrack") as? Data,
let currentTrack = try? JSONDecoder().decode(AudioTrack.self, from: currentTrackData) { let currentTrack = try? JSONDecoder().decode(AudioTrack.self, from: currentTrackData) {
AudioManager.sharedInstance.currentTrack = currentTrack AudioManager.sharedInstance.currentTrack.accept(currentTrack)
} }

@ -0,0 +1,49 @@
//
// Array+IndieMusic.swift
// IndieMusic
//
// Created by WenLei on 2024/3/2.
//
import Foundation
extension Array where Element == AudioTrack {
mutating func updateTracks(by trackId: String, with properties: [PartialKeyPath<AudioTrack>: Any]) {
guard let index = self.firstIndex(where: { $0.id == trackId }) else { return }
for (keyPath, newValue) in properties {
if let keyPath = keyPath as? WritableKeyPath<AudioTrack, Bool?>, let newValue = newValue as? Bool? {
self[index][keyPath: keyPath] = newValue
} else if let keyPath = keyPath as? WritableKeyPath<AudioTrack, String?>, let newValue = newValue as? String? {
self[index][keyPath: keyPath] = newValue
}
}
}
mutating func replaceOrAdd(track newTrack: AudioTrack) {
if let index = self.firstIndex(where: { $0.id == newTrack.id }) {
self[index] = newTrack
} else {
self.append(newTrack)
}
}
func isIdentical(to otherArray: [AudioTrack]) -> Bool {
guard self.count == otherArray.count else { return false }
// 使sortedid
let sortedSelf = self.sorted(by: { $0.id < $1.id })
let sortedOtherArray = otherArray.sorted(by: { $0.id < $1.id })
for (trackFromSelf, trackFromOther) in zip(sortedSelf, sortedOtherArray) {
if trackFromSelf.id != trackFromOther.id {
return false
}
}
return true
}
}

@ -21,6 +21,11 @@ class AudioManager {
let commandCenter = MPRemoteCommandCenter.shared() let commandCenter = MPRemoteCommandCenter.shared()
let currentTrack: BehaviorRelay<AudioTrack?> = .init(value: nil)
let playlist: BehaviorRelay<[AudioTrack]> = .init(value: [])
init() { init() {
NotificationCenter.default NotificationCenter.default
.addObserver(self, .addObserver(self,
@ -48,17 +53,11 @@ class AudioManager {
var player: AVPlayer? var player: AVPlayer?
var currentTrack: AudioTrack?
var currentTrackIndex: Int { var currentTrackIndex: Int {
return playlist?.firstIndex(where: {$0.id == currentTrack?.id}) ?? 0 return playlist.value.firstIndex(where: {$0.id == currentTrack.value?.id}) ?? 0
} }
var playlist: [AudioTrack]?
// var repeatActive = false
// var shuffleActive = false
var playerShuffleType: PlayerShuffleType = .sequential var playerShuffleType: PlayerShuffleType = .sequential
var isSeekInProgress = false var isSeekInProgress = false
@ -67,9 +66,11 @@ class AudioManager {
var isPlayingState = BehaviorSubject<Bool>(value: false) var isPlayingState = BehaviorSubject<Bool>(value: false)
var disposeBag = DisposeBag() var disposeBag = DisposeBag()
var progressObserver = BehaviorRelay<(time: CMTime, duration: CMTime, progress: Float)>.init(value: (time: .zero, duration: .zero, progress: 0))
func setPlaylist(list: [AudioTrack]) { func setPlaylist(list: [AudioTrack]) {
self.playlist = list self.playlist.accept(list)
// //
do { do {
@ -95,15 +96,17 @@ class AudioManager {
let playerItem: AVPlayerItem = AVPlayerItem.init(asset: CachingAVURLAsset(url: url)) let playerItem: AVPlayerItem = AVPlayerItem.init(asset: CachingAVURLAsset(url: url))
self.disposeBag = DisposeBag.init()
player?.pause() player?.pause()
player = AVPlayer(playerItem: playerItem) player = AVPlayer(playerItem: playerItem)
player?.play() player?.play()
player?.volume = 1 player?.volume = 1
self.currentTrack = track self.currentTrack.accept(track)
NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track) NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track)
// updateLockScreenDisplay(track: track)
player?.rx.observe(AVPlayer.TimeControlStatus.self, "timeControlStatus") player?.rx.observe(AVPlayer.TimeControlStatus.self, "timeControlStatus")
.map { $0 == .playing } .map { $0 == .playing }
@ -118,6 +121,20 @@ class AudioManager {
}) })
.disposed(by: disposeBag) .disposed(by: disposeBag)
let interval = CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
self.player?.rx.periodicTimeObserver(interval: interval)
.subscribe(onNext: { [weak self] time in
let currentTime = time
let duration = playerItem.duration
let progress = self?.progress(currentTime: time, duration: duration) ?? 0
self?.progressObserver.accept((time: currentTime, duration: duration, progress: progress))
}).disposed(by: disposeBag)
// //
do { do {
let trackData = try JSONEncoder().encode(track) let trackData = try JSONEncoder().encode(track)
@ -133,8 +150,19 @@ class AudioManager {
} }
} }
func changePlaylistAndPlay(audioTrack: AudioTrack, audioTracks: [AudioTrack]) {
pause()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}
public func nextTrack() { public func nextTrack() {
guard let playlist = playlist else { return } let playlist = playlist.value
switch playerShuffleType { switch playerShuffleType {
case .random: case .random:
@ -164,7 +192,8 @@ class AudioManager {
} }
public func previousTrack() { public func previousTrack() {
if currentTrack != nil, let playlist = playlist { let playlist = playlist.value
if currentTrack.value != nil {
let index = currentTrackIndex - 1 let index = currentTrackIndex - 1
if index >= 0 && index < playlist.count { if index >= 0 && index < playlist.count {
playTrack(track: playlist[index]) playTrack(track: playlist[index])
@ -211,7 +240,7 @@ class AudioManager {
} else { } else {
self.trySeekToChaseTime() self.trySeekToChaseTime()
} }
if let currentTrack = self.currentTrack { if let currentTrack = self.currentTrack.value {
self.updateLockScreenDisplay(track: currentTrack) self.updateLockScreenDisplay(track: currentTrack)
} }
} }
@ -277,7 +306,7 @@ class AudioManager {
commandCenter.playCommand.isEnabled = true commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in commandCenter.playCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, !self.isPlaying() { if let currentTrack = currentTrack.value, !self.isPlaying() {
self.player?.play() self.player?.play()
// updateLockScreenDisplay(track: currentTrack) // updateLockScreenDisplay(track: currentTrack)
return .success return .success
@ -287,7 +316,7 @@ class AudioManager {
commandCenter.pauseCommand.isEnabled = true commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [unowned self] event in commandCenter.pauseCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, self.isPlaying() { if let currentTrack = currentTrack.value, self.isPlaying() {
self.player?.pause() self.player?.pause()
// updateLockScreenDisplay(track: currentTrack) // updateLockScreenDisplay(track: currentTrack)
return .success return .success
@ -326,4 +355,33 @@ class AudioManager {
} }
private func progress(currentTime: CMTime, duration: CMTime) -> Float {
if !duration.isValid || !currentTime.isValid {
return 0
}
let totalSeconds = duration.seconds
let currentSeconds = currentTime.seconds
if !totalSeconds.isFinite || !currentSeconds.isFinite {
return 0
}
let p = Float(min(currentSeconds/totalSeconds, 1))
return p
}
}
extension AudioManager {
func updateTrack<T>(in tracks: inout [AudioTrack], by trackId: String, property: WritableKeyPath<AudioTrack, T>, to newValue: T) {
// id
if let index = tracks.firstIndex(where: { $0.id == trackId }) {
// 使KeyPath
tracks[index][keyPath: property] = newValue
}
}
} }

@ -71,3 +71,23 @@ extension JournalSection: SectionModelType {
} }
} }
extension JournalSection {
var allAudioTracks: [AudioTrack] {
let items: [JournalItem] = {
switch self {
case .audioSection(_, let items), .journalSection(_, let items):
return items
}
}()
return items.compactMap { item -> AudioTrack? in
switch item {
case .audioItem(let model):
return model
case .journaItem:
return nil
}
}
}
}

@ -189,7 +189,7 @@ class HomeTabBarController: UITabBarController, Navigatable {
playerTabBar.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in playerTabBar.rx.controlEvent(.touchUpInside).subscribe { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
guard let track = AudioManager.sharedInstance.currentTrack else { return } guard let track = AudioManager.sharedInstance.currentTrack.value else { return }
let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider)
self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
@ -198,7 +198,7 @@ class HomeTabBarController: UITabBarController, Navigatable {
output.notiPlayAudioTrack.subscribe { [weak self] noti in output.notiPlayAudioTrack.subscribe { [weak self] noti in
self?.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack self?.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack.value
self?.playerTabBar.playButton.isSelected = true self?.playerTabBar.playButton.isSelected = true
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -208,15 +208,19 @@ class HomeTabBarController: UITabBarController, Navigatable {
output.notiPlayPause.subscribe { [weak self] noti in output.notiPlayPause.subscribe { [weak self] noti in
self?.playerTabBar.playButton.isSelected = false self?.playerTabBar.playButton.isSelected = false
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
output.progress.subscribe { [weak self] progress in // output.progress.subscribe { [weak self] progress in
self?.playerTabBar.progressView.progress = progress // self?.playerTabBar.progressView.progress = progress
}.disposed(by: rx.disposeBag) // }.disposed(by: rx.disposeBag)
AudioManager.sharedInstance.progressObserver.subscribe { [weak self] (time, duration, progress) in
self?.playerTabBar.progressView.progress = Float(progress)
}.disposed(by: rx.disposeBag)
} }
override func setViewControllers(_ viewControllers: [UIViewController]?, animated: Bool) { override func setViewControllers(_ viewControllers: [UIViewController]?, animated: Bool) {

@ -57,9 +57,17 @@ class HomeTabBarViewModel: ViewModel, ViewModelType {
checkVersion() checkVersion()
AudioManager.sharedInstance.progressObserver.subscribe { (time, duration, progress) in
}.disposed(by: rx.disposeBag)
input.notiPlayAudioTrack.subscribe { [weak self] noti in input.notiPlayAudioTrack.subscribe { [weak self] noti in
guard let track = noti.element?.object as? AudioTrack else { return } guard let track = noti.element?.object as? AudioTrack else { return }
// AudioManager.sharedInstance.player?.addProgressObserver { currentTime, duration, progress in // AudioManager.sharedInstance.player?.addProgressObserver { currentTime, duration, progress in
// self?.progress.accept(Float(progress)) // self?.progress.accept(Float(progress))
// self?.duration.accept(Float(duration)) // self?.duration.accept(Float(duration))

@ -56,7 +56,7 @@ class HomeViewController: TableViewController, ScrollableNavBar {
if tabbar.customeTabBar.isHidden == true && tabbar.playerTabBar.isHidden == true { if tabbar.customeTabBar.isHidden == true && tabbar.playerTabBar.isHidden == true {
tabbar.showAllBar(true, animated: true) tabbar.showAllBar(true, animated: true)
} else if tabbar.playerTabBar.isHidden == true { } else if tabbar.playerTabBar.isHidden == true {
tabbar.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack tabbar.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack.value
tabbar.showPlayerBar(true, animated: true) tabbar.showPlayerBar(true, animated: true)
} }
} }
@ -65,7 +65,7 @@ class HomeViewController: TableViewController, ScrollableNavBar {
navigationBar.passthroughView = homePagerView navigationBar.passthroughView = homePagerView
} }
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 { if AudioManager.sharedInstance.playlist.value.count > 0 {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0) tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else { } else {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0) tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)
@ -192,12 +192,7 @@ class HomeViewController: TableViewController, ScrollableNavBar {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTrackArray)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTrackArray)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -79,7 +79,7 @@ class SongViewCell: UITableViewCell {
coverView.kf.setImage(with: URL.init(string: audioTrack.pic ?? "")) coverView.kf.setImage(with: URL.init(string: audioTrack.pic ?? ""))
if AudioManager.sharedInstance.currentTrack?.id == audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == audioTrack.id {
musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
@ -87,7 +87,7 @@ class SongViewCell: UITableViewCell {
} }
AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in
if AudioManager.sharedInstance.currentTrack?.id == audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == audioTrack.id {
self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
self?.musicIndicator.state = .stopped self?.musicIndicator.state = .stopped

@ -38,6 +38,7 @@ class JournalDetailController: TableViewController {
super.viewWillAppear(animated) super.viewWillAppear(animated)
updateNav(navBarBgAlpha: self.currentNavBarBgAlpha) updateNav(navBarBgAlpha: self.currentNavBarBgAlpha)
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
@ -118,6 +119,23 @@ class JournalDetailController: TableViewController {
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag) output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.items.subscribe { journalSections in
guard let currentAudioTracks = journalSections.first?.allAudioTracks else { return }
let audioTracks = AudioManager.sharedInstance.playlist.value
if currentAudioTracks.isIdentical(to: audioTracks) {
if AudioManager.sharedInstance.isPlaying() {
self.journalCollapsibleHeaderView.playButton.isSelected = true
} else {
self.journalCollapsibleHeaderView.playButton.isSelected = false
}
} else {
self.journalCollapsibleHeaderView.playButton.isSelected = false
}
}.disposed(by: rx.disposeBag)
output.modelSelected.drive {[weak self] journalItem in output.modelSelected.drive {[weak self] journalItem in
self?.deselectSelectedRow() self?.deselectSelectedRow()
@ -142,12 +160,7 @@ class JournalDetailController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: track, audioTracks: audioTracks)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: track)
}
case .journaItem(_): break case .journaItem(_): break
} }
@ -170,6 +183,8 @@ class JournalDetailController: TableViewController {
guard let self = self else { return } guard let self = self else { return }
self.commentToolView.commentCountButton.count = Int(journal.totalCommentReply ?? "") self.commentToolView.commentCountButton.count = Int(journal.totalCommentReply ?? "")
self.journalCollapsibleHeaderView.journal = journal self.journalCollapsibleHeaderView.journal = journal
self.updateHeader() self.updateHeader()
@ -202,9 +217,24 @@ class JournalDetailController: TableViewController {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
journalCollapsibleHeaderView.playButton.rx.tap.subscribe { [weak self] _ in journalCollapsibleHeaderView.playButton.rx.tap.subscribe { [weak self] _ in
guard let audioTracks = output.items.value.first?.allAudioTracks,
let audioTrack = audioTracks.first else { return }
SVProgressHUD.showText(withStatus: "播放分模式") if AudioManager.sharedInstance.playlist.value.isIdentical(to: audioTracks) {
if AudioManager.sharedInstance.isPlaying() {
AudioManager.sharedInstance.pause()
} else {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}
} else {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -234,7 +264,20 @@ class JournalDetailController: TableViewController {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
// AudioManager.sharedInstance. AudioManager.sharedInstance.isPlayingState.subscribe { isPlaying in
guard let currentListAudioTracks = output.items.value.first?.allAudioTracks else { return }
let audioTracks = AudioManager.sharedInstance.playlist.value
if currentListAudioTracks.isIdentical(to: audioTracks) && isPlaying {
self.journalCollapsibleHeaderView.playButton.isSelected = true
} else {
self.journalCollapsibleHeaderView.playButton.isSelected = false
}
}.disposed(by: rx.disposeBag)
tableView.rx.willDisplayCell tableView.rx.willDisplayCell
.subscribe { [weak self] cell, indexPath in .subscribe { [weak self] cell, indexPath in

@ -209,7 +209,7 @@ class JournalCollapsibleHeaderView: UIView {
playButton.isSelected = AudioManager.sharedInstance.currentTrack?.journalNo == journal.journalNo // playButton.isSelected = AudioManager.sharedInstance.currentTrack.value?.journalNo == journal.journalNo
} }
} }
@ -619,7 +619,7 @@ class JournalAudioViewCell: UITableViewCell {
coverView.kf.setImage(with: URL.init(string: audioTrack.pic ?? "")) coverView.kf.setImage(with: URL.init(string: audioTrack.pic ?? ""))
if AudioManager.sharedInstance.currentTrack?.id == audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == audioTrack.id {
musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
@ -627,7 +627,7 @@ class JournalAudioViewCell: UITableViewCell {
} }
AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in
if AudioManager.sharedInstance.currentTrack?.id == audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == audioTrack.id {
self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
self?.musicIndicator.state = .stopped self?.musicIndicator.state = .stopped

@ -67,6 +67,9 @@ class JournalDetailViewModel: ViewModel, ViewModelType {
input.viewWillAppear.subscribe { _ in input.viewWillAppear.subscribe { _ in
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -123,13 +123,7 @@ class MineDownloadViewController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -146,13 +140,7 @@ class MineDownloadViewController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -337,7 +325,7 @@ class MineDownloadViewCell: UITableViewCell {
selectButton.isSelected = mineDownload.isSelected selectButton.isSelected = mineDownload.isSelected
if AudioManager.sharedInstance.currentTrack?.id == mineDownload.audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == mineDownload.audioTrack.id {
musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
@ -345,7 +333,7 @@ class MineDownloadViewCell: UITableViewCell {
} }
AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in AudioManager.sharedInstance.isPlayingState.subscribe { [weak self] isPlaying in
if AudioManager.sharedInstance.currentTrack?.id == mineDownload.audioTrack.id { if AudioManager.sharedInstance.currentTrack.value?.id == mineDownload.audioTrack.id {
self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused self?.musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else { } else {
self?.musicIndicator.state = .stopped self?.musicIndicator.state = .stopped

@ -11,6 +11,11 @@ import RxCocoa
import RxDataSources import RxDataSources
class MineJournalViewController: CollectionViewController { class MineJournalViewController: CollectionViewController {
let headerView: MineSingleHeaderView = {
let headerView = MineSingleHeaderView.init()
return headerView
}()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -42,6 +47,7 @@ class MineJournalViewController: CollectionViewController {
emptyDataSetDescription = "你还没收藏过期刊!" emptyDataSetDescription = "你还没收藏过期刊!"
view.addSubview(headerView)
} }
override func bindViewModel() { override func bindViewModel() {
@ -54,7 +60,8 @@ class MineJournalViewController: CollectionViewController {
let input = MineJournalViewModel.Input.init(viewWillAppear: rx.viewWillAppear, let input = MineJournalViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
headerRefresh: refresh, headerRefresh: refresh,
footerRefresh: footerRefreshTrigger, footerRefresh: footerRefreshTrigger,
selection: collectionView.rx.itemSelected.asDriver()) selection: collectionView.rx.itemSelected.asDriver(),
journalSongTrigger: headerView.playAllButton.rx.tap.asDriver())
let output = viewModel.transform(input: input) let output = viewModel.transform(input: input)
@ -73,10 +80,16 @@ class MineJournalViewController: CollectionViewController {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
let updateEmptyDataSet = Observable.of(isLoading.mapToVoid().asObservable(), emptyDataSetImageTintColor.mapToVoid(), languageChanged.asObservable()).merge() output.toPlay.subscribe { [weak self] audioTracks in
updateEmptyDataSet.subscribe(onNext: { [weak self] () in guard let audioTracks = audioTracks.element,
self?.collectionView.reloadEmptyDataSet() let audioTrack = audioTracks.first else { return }
}).disposed(by: rx.disposeBag)
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}.disposed(by: rx.disposeBag)
} }
@ -85,14 +98,20 @@ class MineJournalViewController: CollectionViewController {
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
headerView.snp.remakeConstraints { make in
collectionView.snp.makeConstraints { make in
make.left.equalTo(view) make.left.equalTo(view)
make.right.equalTo(view) make.right.equalTo(view)
make.top.equalTo(view) make.top.equalTo(view)
make.height.equalTo(64)
}
collectionView.snp.remakeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(headerView.snp.bottom)
make.bottom.equalTo(view) make.bottom.equalTo(view)
} }
} }
} }

@ -16,18 +16,20 @@ class MineJournalViewModel: ViewModel, ViewModelType {
let headerRefresh: Observable<Void> let headerRefresh: Observable<Void>
let footerRefresh: Observable<Void> let footerRefresh: Observable<Void>
let selection: Driver<IndexPath> let selection: Driver<IndexPath>
let journalSongTrigger: Driver<Void>
} }
struct Output { struct Output {
let items: BehaviorRelay<[MineJournalSection]> let items: BehaviorRelay<[MineJournalSection]>
let selection: Driver<IndexPath> let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Journal> let itemSelected: PublishSubject<Journal>
let toPlay: PublishRelay<[AudioTrack]>
} }
let itemSelected = PublishSubject<Journal>() let itemSelected = PublishSubject<Journal>()
let items = BehaviorRelay<[MineJournalSection]>.init(value: []) let items = BehaviorRelay<[MineJournalSection]>.init(value: [])
// let item = BehaviorRelay<Journal?>.init(value: nil) // let item = BehaviorRelay<Journal?>.init(value: nil)
let toPlay = PublishRelay<[AudioTrack]>.init()
func transform(input: Input) -> Output { func transform(input: Input) -> Output {
@ -69,11 +71,24 @@ class MineJournalViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
input.journalSongTrigger.drive { _ in
self.requestCollectJournalSongList(page: 0, size: 0)
.subscribe(onNext: { audioTracks in
self.toPlay.accept(audioTracks)
}, onError: { error in
}).disposed(by: self.rx.disposeBag)
}.disposed(by: rx.disposeBag)
return Output.init(items: items, return Output.init(items: items,
selection: input.selection, selection: input.selection,
itemSelected: itemSelected itemSelected: itemSelected,
toPlay: self.toPlay
) )
} }
@ -85,5 +100,11 @@ class MineJournalViewModel: ViewModel, ViewModelType {
} }
func requestCollectJournalSongList( page: Int, size: Int) -> Observable<[AudioTrack]> {
return self.provider.collectJournalSongList(userId: UserDefaults.AccountInfo.string(forKey: .userID) ?? "", page: page, size: size)
.trackActivity(loading)
.trackError(error)
}
} }

@ -96,13 +96,7 @@ class MineSingleController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: track, audioTracks: audioModels)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioModels)
AudioManager.sharedInstance.playTrack(track: track)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
@ -117,13 +111,7 @@ class MineSingleController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -45,7 +45,7 @@ class MineViewController: TableViewController {
tabbar.showAllBar(true, animated: true) tabbar.showAllBar(true, animated: true)
} }
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 { if AudioManager.sharedInstance.playlist.value.count > 0 {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0) tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else { } else {
tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0) tableView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)
@ -233,13 +233,7 @@ class MineViewController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -228,12 +228,7 @@ class PersonalSongViewController: CollectionViewController, UIScrollViewDelegate
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: [audioTrack])
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AudioManager.sharedInstance.setPlaylist(list: [audioTrack])
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
default: break default: break
} }

@ -23,6 +23,7 @@ class AudioTrackListViewController: ViewController {
tableView.register(SongViewCell.self tableView.register(SongViewCell.self
, forCellReuseIdentifier: "SongViewCell") , forCellReuseIdentifier: "SongViewCell")
tableView.separatorColor = .clear tableView.separatorColor = .clear
tableView.backgroundColor = .clear
return tableView return tableView
}() }()
@ -48,11 +49,6 @@ class AudioTrackListViewController: ViewController {
view.addSubview(tableView) view.addSubview(tableView)
view.addSubview(audioMoreActionBottomView) view.addSubview(audioMoreActionBottomView)
let haederView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: BaseDimensions.screenWidth, height: 9))
haederView.backgroundColor = .white
tableView.tableHeaderView = haederView
view.backgroundColor = .white
} }
@ -144,6 +140,7 @@ extension AudioTrackListViewController {
return RxTableViewSectionedReloadDataSource<JournalSection>( return RxTableViewSectionedReloadDataSource<JournalSection>(
configureCell: { dataSource, tableView, indexPath, item in configureCell: { dataSource, tableView, indexPath, item in
let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell
cell.backgroundColor = .clear
if case let .audioItem(model) = item { if case let .audioItem(model) = item {
cell.audioTrack = model cell.audioTrack = model

@ -37,12 +37,12 @@ class AudioTrackListViewModel: ViewModel, ViewModelType {
input.viewWillAppear.subscribe { [weak self] isViewWillAppear in input.viewWillAppear.subscribe { [weak self] isViewWillAppear in
guard let self = self else { return } guard let self = self else { return }
if let playlist = AudioManager.sharedInstance.playlist { let playlist = AudioManager.sharedInstance.playlist.value
let arr = playlist.map { audioTrack in let arr = playlist.map { audioTrack in
return JournalItem.audioItem(model: audioTrack) return JournalItem.audioItem(model: audioTrack)
} }
self.items.accept([JournalSection.audioSection(header: nil, items: arr)]) self.items.accept([JournalSection.audioSection(header: nil, items: arr)])
}
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)

@ -218,9 +218,11 @@ class PlayerTabBar: UIControl {
// AudioManager.sharedInstance.player?.addProgressObserver { time, duration, progress in AudioManager.sharedInstance.progressObserver.subscribe { (time, duration, progress) in
// self.progressView.setProgress(Float(progress), animated: false) self.progressView.setProgress(Float(progress), animated: false)
// }
}.disposed(by: rx.disposeBag)
// self.playButton.image = UIImage(named: "pause") // self.playButton.image = UIImage(named: "pause")
} }

@ -162,25 +162,25 @@ class PlayerViewController: ViewController {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
output.progress.subscribe { [weak self] progress in // output.progress.subscribe { [weak self] progress in
guard let self = self else { return } // guard let self = self else { return }
//
if !self.playerScrollView.playerInfoView.playerSlider.isDrop { // if !self.playerScrollView.playerInfoView.playerSlider.isDrop {
self.playerScrollView.playerInfoView.playerSlider.slider.value = progress // self.playerScrollView.playerInfoView.playerSlider.slider.value = progress
} // }
}.disposed(by: rx.disposeBag) // }.disposed(by: rx.disposeBag)
//
output.duration.subscribe { [weak self] duration in // output.duration.subscribe { [weak self] duration in
guard let self = self else { return } // guard let self = self else { return }
//
self.playerScrollView.playerInfoView.playerSlider.totalSec = duration // self.playerScrollView.playerInfoView.playerSlider.totalSec = duration
}.disposed(by: rx.disposeBag) // }.disposed(by: rx.disposeBag)
//
output.time.subscribe { [weak self] time in // output.time.subscribe { [weak self] time in
guard let self = self else { return } // guard let self = self else { return }
//
self.playerScrollView.playerInfoView.playerSlider.updateTimeLabel(currentTime: time) // self.playerScrollView.playerInfoView.playerSlider.updateTimeLabel(currentTime: time)
}.disposed(by: rx.disposeBag) // }.disposed(by: rx.disposeBag)
output.isPlaying.bind(to: self.playerControlView.playButton.rx.isSelected).disposed(by: rx.disposeBag) output.isPlaying.bind(to: self.playerControlView.playButton.rx.isSelected).disposed(by: rx.disposeBag)
@ -207,6 +207,17 @@ class PlayerViewController: ViewController {
}.disposed(by: rx.disposeBag) }.disposed(by: rx.disposeBag)
AudioManager.sharedInstance.progressObserver.subscribe { (time, duration, progress) in
if !self.playerScrollView.playerInfoView.playerSlider.isDrop {
self.playerScrollView.playerInfoView.playerSlider.slider.value = Float(progress)
}
self.playerScrollView.playerInfoView.playerSlider.totalSec = Float(duration.seconds)
self.playerScrollView.playerInfoView.playerSlider.updateTimeLabel(currentTime: Float(time.seconds))
}.disposed(by: rx.disposeBag)
} }
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {

@ -219,11 +219,10 @@ class PlayerViewModel: ViewModel, ViewModelType {
func updateAudioTrack(audioTrack: AudioTrack) { func updateAudioTrack(audioTrack: AudioTrack) {
self.currentAudioTrack.accept(audioTrack) self.currentAudioTrack.accept(audioTrack)
var playlist = AudioManager.sharedInstance.playlist.value
playlist.replaceOrAdd(track: audioTrack)
if let index = AudioManager.sharedInstance.playlist?.firstIndex(where: { $0.id == audioTrack.id }) { AudioManager.sharedInstance.playlist.accept(playlist)
// 使 AudioTrack
AudioManager.sharedInstance.playlist?[index] = audioTrack
}
} }

@ -225,12 +225,7 @@ class SongResultsViewController: CollectionViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider) let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal) self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: [audioTrack])
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AudioManager.sharedInstance.setPlaylist(list: [audioTrack])
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
default: break default: break
} }

@ -59,7 +59,7 @@ class SearchViewController: ViewController, UIScrollViewDelegate {
self.navigationController?.setNavigationBarHidden(true, animated: false) self.navigationController?.setNavigationBarHidden(true, animated: false)
if AudioManager.sharedInstance.playlist?.count ?? 0 > 0 { if AudioManager.sharedInstance.playlist.value.count > 0 {
collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0) collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight + 66, right: 0)
} else { } else {
collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0) collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)

@ -53,6 +53,9 @@ protocol IndieMusicAPI {
func messageList() -> Single<MessageList> func messageList() -> Single<MessageList>
/// ///
func collectSongList(userId: String, page: Int, size: Int) -> Single<[AudioTrack]> func collectSongList(userId: String, page: Int, size: Int) -> Single<[AudioTrack]>
///
func collectJournalSongList(userId: String, page: Int, size: Int) -> Single<[AudioTrack]>
/// ///
func journalCollectList(userId: String, page: Int, size: Int) -> Single<[Journal]> func journalCollectList(userId: String, page: Int, size: Int) -> Single<[Journal]>
/// ///

@ -36,6 +36,7 @@ enum APIConfig {
case messageList case messageList
case collectSongList([String: Any]) case collectSongList([String: Any])
case collectJournalSongList([String: Any])
case journalCollectList([String: Any]) case journalCollectList([String: Any])
case followingList(String, Int, Int) case followingList(String, Int, Int)
case followerList(String, Int, Int) case followerList(String, Int, Int)
@ -110,6 +111,8 @@ extension APIConfig: TargetType {
return "luoo-user/userMessage/list/" return "luoo-user/userMessage/list/"
case .collectSongList: case .collectSongList:
return "luoo-music/song/collect" return "luoo-music/song/collect"
case .collectJournalSongList:
return "luoo-music/journal/collect/song"
case .journalCollectList: case .journalCollectList:
return "luoo-music/journal/collect" return "luoo-music/journal/collect"
case .followingList(let userId, let page, let size): case .followingList(let userId, let page, let size):
@ -166,7 +169,7 @@ extension APIConfig: TargetType {
var method: Moya.Method { var method: Moya.Method {
switch self { switch self {
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .searchSong, .searchJournal, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions, .thanks: case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .searchSong, .searchJournal, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions, .thanks, .collectJournalSongList:
return .get return .get
case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment, .feedback, .commentReport: case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment, .feedback, .commentReport:
return .post return .post
@ -180,7 +183,7 @@ extension APIConfig: TargetType {
var parameterEncoding: ParameterEncoding { var parameterEncoding: ParameterEncoding {
switch self { switch self {
case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .sendsms, .imageCheckCode, .login, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .like, .cancelLike, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .searchSong, .searchJournal, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions, .commentReport, .deleteComment, .thanks: case .wechatAccessToken, .journalList, .journalMusic, .countryCode, .sendsms, .imageCheckCode, .login, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .like, .cancelLike, .collectSongList, .journalCollectList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .searchSong, .searchJournal, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .suggestions, .commentReport, .deleteComment, .thanks, .collectJournalSongList:
return URLEncoding.default return URLEncoding.default
case .autoLogin, .editUserInfo, .editAvatar, .checkVersion, .logout, .commentLike, .sendComment, .feedback: case .autoLogin, .editUserInfo, .editAvatar, .checkVersion, .logout, .commentLike, .sendComment, .feedback:
@ -195,7 +198,7 @@ extension APIConfig: TargetType {
case .wechatAccessToken, .countryCode, .journalMusic, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .commentReport, .deleteComment, .thanks: case .wechatAccessToken, .countryCode, .journalMusic, .imageCheckCode, .getUserInfo, .carousel, .otherUserInfo, .single, .journal, .messageList, .followingList, .followerList, .blackList, .hotCommentList, .latestCommentList, .subCommentList, .filterMenu, .journalRecommend, .searchCategory, .randomAudioTrack, .myThumbupList, .myCommentReplyList, .commentReport, .deleteComment, .thanks:
return .requestPlain return .requestPlain
case .login(let dic), .journalList(let dic), .sendsms(let dic), .autoLogin(let dic), .editUserInfo(let dic), .like(let dic), .cancelLike(let dic), .logout(let dic), .checkVersion(let dic), .commentLike(_, let dic), .sendComment(let dic), .collectSongList(let dic), .journalCollectList(let dic), .suggestions(let dic), .searchSong(let dic), .searchJournal(let dic): case .login(let dic), .journalList(let dic), .sendsms(let dic), .autoLogin(let dic), .editUserInfo(let dic), .like(let dic), .cancelLike(let dic), .logout(let dic), .checkVersion(let dic), .commentLike(_, let dic), .sendComment(let dic), .collectSongList(let dic), .journalCollectList(let dic), .suggestions(let dic), .searchSong(let dic), .searchJournal(let dic), .collectJournalSongList(let dic):
parameters = dic parameters = dic
return .requestParameters(parameters: parameters, encoding: parameterEncoding) return .requestParameters(parameters: parameters, encoding: parameterEncoding)
@ -238,7 +241,7 @@ extension APIConfig: TargetType {
var headers : [String : String]? { var headers : [String : String]? {
switch self { switch self {
case .autoLogin, .getUserInfo, .journalList, .journalMusic, .otherUserInfo, .like, .cancelLike, .single, .journal, .collectSongList, .journalCollectList, .followingList, .messageList, .followerList, .blackList, .editUserInfo, .logout, .editAvatar, .hotCommentList, .latestCommentList, .subCommentList, .commentLike, .filterMenu, .journalRecommend, .searchSong, .searchJournal, .randomAudioTrack, .sendComment, .myThumbupList, .myCommentReplyList, .feedback, .suggestions, .commentReport, .deleteComment: case .autoLogin, .getUserInfo, .journalList, .journalMusic, .otherUserInfo, .like, .cancelLike, .single, .journal, .collectSongList, .journalCollectList, .followingList, .messageList, .followerList, .blackList, .editUserInfo, .logout, .editAvatar, .hotCommentList, .latestCommentList, .subCommentList, .commentLike, .filterMenu, .journalRecommend, .searchSong, .searchJournal, .randomAudioTrack, .sendComment, .myThumbupList, .myCommentReplyList, .feedback, .suggestions, .commentReport, .deleteComment, .collectJournalSongList:
return ["Authorization": AuthManager.shared.token?.basicToken ?? ""] return ["Authorization": AuthManager.shared.token?.basicToken ?? ""]
default: default:
return nil return nil

@ -215,6 +215,16 @@ extension RestApi {
return requestObject(.collectSongList(dic), with: "data.rows", type: [AudioTrack].self) return requestObject(.collectSongList(dic), with: "data.rows", type: [AudioTrack].self)
} }
func collectJournalSongList(userId: String, page: Int, size: Int) -> Single<[AudioTrack]> {
let dic = ["userId": userId,
"pageNum": page,
"pageSize": size
] as [String : Any]
return requestObject(.collectJournalSongList(dic), with: "data.rows", type: [AudioTrack].self)
}
func journalCollectList(userId: String, page: Int, size: Int) -> Single<[Journal]> { func journalCollectList(userId: String, page: Int, size: Int) -> Single<[Journal]> {
let dic = ["userId": userId, let dic = ["userId": userId,
"pageNum": page, "pageNum": page,

@ -0,0 +1,28 @@
//
// AVAsynchronousKeyValueLoading+Rx.swift
// RxAVPlayer
//
// Created by Patrick Mick on 4/1/16.
// Copyright © 2016 YayNext. All rights reserved.
//
import Foundation
import AVFoundation
import RxSwift
import RxCocoa
extension Reactive where Base: AVAsynchronousKeyValueLoading {
public func loadValuesForKeys(_ keys: [String]) -> Observable<Void> {
return Observable.create { observer in
self.base.loadValuesAsynchronously(forKeys: keys) {
// TODO: Test statusOfValueForKey for every key that was loaded and
// return some kind of error model if any keys failed to load
observer.onNext(())
observer.onCompleted()
}
return Disposables.create()
}
}
}

@ -0,0 +1,64 @@
//
// AVPlayer+Rx.swift
// RxAVPlayer
//
// Created by Patrick Mick on 3/30/16.
// Copyright © 2016 YayNext. All rights reserved.
//
import Foundation
import AVFoundation
import RxSwift
import RxCocoa
extension Reactive where Base: AVPlayer {
public var rate: Observable<Float> {
return self.observe(Float.self, #keyPath(AVPlayer.rate))
.map { $0 ?? 0 }
}
public var currentItem: Observable<AVPlayerItem?> {
return observe(AVPlayerItem.self, #keyPath(AVPlayer.currentItem))
}
public var status: Observable<AVPlayer.Status> {
return self.observe(AVPlayer.Status.self, #keyPath(AVPlayer.status))
.map { $0 ?? .unknown }
}
public var error: Observable<NSError?> {
return self.observe(NSError.self, #keyPath(AVPlayer.error))
}
@available(iOS 10.0, tvOS 10.0, macOS 10.12, *)
public var reasonForWaitingToPlay: Observable<AVPlayer.WaitingReason?> {
return self.observe(AVPlayer.WaitingReason.self, #keyPath(AVPlayer.reasonForWaitingToPlay))
}
@available(iOS 10.0, tvOS 10.0, *, OSX 10.12, *)
public var timeControlStatus: Observable<AVPlayer.TimeControlStatus> {
return self.observe(AVPlayer.TimeControlStatus.self, #keyPath(AVPlayer.timeControlStatus))
.map { $0 ?? .waitingToPlayAtSpecifiedRate }
}
public func periodicTimeObserver(interval: CMTime) -> Observable<CMTime> {
return Observable.create { observer in
let t = self.base.addPeriodicTimeObserver(forInterval: interval, queue: nil) { time in
observer.onNext(time)
}
return Disposables.create { self.base.removeTimeObserver(t) }
}
}
public func boundaryTimeObserver(times: [CMTime]) -> Observable<Void> {
return Observable.create { observer in
let timeValues = times.map() { NSValue(time: $0) }
let t = self.base.addBoundaryTimeObserver(forTimes: timeValues, queue: nil) {
observer.onNext(())
}
return Disposables.create { self.base.removeTimeObserver(t) }
}
}
}

@ -0,0 +1,54 @@
//
// AVPlayerItem+Rx.swift
// RxAVPlayer
//
// Created by Patrick Mick on 4/1/16.
// Copyright © 2016 YayNext. All rights reserved.
//
import Foundation
import AVFoundation
import RxSwift
import RxCocoa
extension Reactive where Base: AVPlayerItem {
public var status: Observable<AVPlayerItem.Status> {
return self.observe(AVPlayerItem.Status.self, #keyPath(AVPlayerItem.status))
.map { $0 ?? .unknown }
}
public var error: Observable<NSError?> {
return self.observe(NSError.self, #keyPath(AVPlayerItem.error))
}
public var duration: Observable<CMTime> {
return self.observe(CMTime.self, #keyPath(AVPlayerItem.duration))
.map { $0 ?? .zero }
}
public var playbackLikelyToKeepUp: Observable<Bool> {
return self.observe(Bool.self, #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp))
.map { $0 ?? false }
}
public var playbackBufferFull: Observable<Bool> {
return self.observe(Bool.self, #keyPath(AVPlayerItem.isPlaybackBufferFull))
.map { $0 ?? false }
}
public var playbackBufferEmpty: Observable<Bool> {
return self.observe(Bool.self, #keyPath(AVPlayerItem.isPlaybackBufferEmpty))
.map { $0 ?? false }
}
public var didPlayToEnd: Observable<Notification> {
let ns = NotificationCenter.default
return ns.rx.notification(.AVPlayerItemDidPlayToEndTime, object: base)
}
public var loadedTimeRanges: Observable<[CMTimeRange]> {
return self.observe([NSValue].self, #keyPath(AVPlayerItem.loadedTimeRanges))
.map { $0 ?? [] }
.map { values in values.map { $0.timeRangeValue } }
}
}

@ -0,0 +1,24 @@
//
// AVPlayerLayer+Rx.swift
// RxAVPlayer
//
// Created by Patrick Mick on 4/4/16.
// Copyright © 2016 YayNext. All rights reserved.
//
import Foundation
import AVFoundation
import RxSwift
import RxCocoa
extension Reactive where Base: AVPlayerLayer {
public var isReadyForDisplay: Observable<Bool> {
return self.observe(Bool.self, #keyPath(AVPlayerLayer.isReadyForDisplay))
.map { $0 ?? false }
}
@available(*, deprecated, renamed: "isReadyForDisplay")
public var readyForDisplay: Observable<Bool> {
isReadyForDisplay
}
}
Loading…
Cancel
Save