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 */; };
770228F92B5A224500E07F7A /* SliderControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228F62B5A224500E07F7A /* SliderControl.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 */; };
771233EE2B6B8CE6009FAF01 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771233ED2B6B8CE6009FAF01 /* NavigationBar.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>"; };
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>"; };
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>"; };
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>"; };
@ -577,6 +587,17 @@
path = Combine;
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 */ = {
isa = PBXGroup;
children = (
@ -821,6 +842,7 @@
77AC35742B6E3D8300D046C2 /* UIView+IndieMusic.swift */,
7751D3852B45409000F1F2BD /* NSNotification+IndieMusic.swift */,
7751009B2B62065800F46109 /* Date+IndieMusic.swift */,
770FC3EE2B92C8130023DE28 /* Array+IndieMusic.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -910,6 +932,7 @@
778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = {
isa = PBXGroup;
children = (
770FC3F02B92F42D0023DE28 /* RxAVFoundation */,
775D07562B5E47C4009270D3 /* ETPopupView */,
77A60D7A2B5B975600D4E435 /* ResourceLoader */,
770228F22B5A224500E07F7A /* SliderControl */,
@ -1442,6 +1465,7 @@
77C9B9DF2B4BCE300006C83F /* Message.swift in Sources */,
77FA0B2C2B0C480B00404C5E /* AudioMoreActionViewModel.swift in Sources */,
778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */,
770FC3F52B92F4380023DE28 /* AVAsynchronousKeyValueLoading+Rx.swift in Sources */,
77620D8D2B68844E00798861 /* Followers.swift in Sources */,
77FA0B562B0F4ABF00404C5E /* MineSingleController.swift in Sources */,
778B8A9D2AF8ECFC0034AFD4 /* LibsManager.swift in Sources */,
@ -1504,6 +1528,7 @@
778B8A862AF8ECE50034AFD4 /* SearchViewModel.swift in Sources */,
77C9B9DD2B4BC5D20006C83F /* FollowersViewModel.swift in Sources */,
778B8A802AF8ECE50034AFD4 /* MineViewController.swift in Sources */,
770FC3EF2B92C8130023DE28 /* Array+IndieMusic.swift in Sources */,
778B8A8F2AF8ECF20034AFD4 /* EmptyModel.swift in Sources */,
778B8ABD2AF8ED280034AFD4 /* View.swift in Sources */,
778B8A912AF8ECF20034AFD4 /* Album.swift in Sources */,
@ -1570,6 +1595,7 @@
7751D3762B43E8D200F1F2BD /* MusicStyle.swift in Sources */,
77C9B9D72B4BBD780006C83F /* FollowingViewModel.swift in Sources */,
7736FF442B4CECF2008D5DAD /* CommentViewModel.swift in Sources */,
770FC3F62B92F4380023DE28 /* AVPlayer+Rx.swift in Sources */,
77620D962B68E96C00798861 /* DateItem.swift in Sources */,
778B8AA92AF8ED0E0034AFD4 /* UIImage+IndieMusic.swift in Sources */,
77C9B9D12B4B99600006C83F /* BadgeButton.swift in Sources */,
@ -1596,6 +1622,7 @@
7751D37C2B4516BE00F1F2BD /* UILabel+IndieMusic.swift in Sources */,
778B8AAC2AF8ED0E0034AFD4 /* UIViewController+Rx.swift in Sources */,
778B8A8D2AF8ECF20034AFD4 /* APIImage.swift in Sources */,
770FC3F72B92F4380023DE28 /* AVPlayerItem+Rx.swift in Sources */,
770228F92B5A224500E07F7A /* SliderControl.swift in Sources */,
7751D36A2B42ED6C00F1F2BD /* TimingViewController.swift in Sources */,
77FA0B462B0DFAFD00404C5E /* BindPhoneViewController.swift in Sources */,
@ -1610,6 +1637,7 @@
77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */,
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */,
77CEFEFE2B82F18A0071B671 /* CommentToolView.swift in Sources */,
770FC3F82B92F4380023DE28 /* AVPlayerLayer+Rx.swift in Sources */,
7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */,
77A60D822B5B97A100D4E435 /* AssetDataManager.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,
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 currentTrack: BehaviorRelay<AudioTrack?> = .init(value: nil)
let playlist: BehaviorRelay<[AudioTrack]> = .init(value: [])
init() {
NotificationCenter.default
.addObserver(self,
@ -48,17 +53,11 @@ class AudioManager {
var player: AVPlayer?
var currentTrack: AudioTrack?
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 isSeekInProgress = false
@ -67,9 +66,11 @@ class AudioManager {
var isPlayingState = BehaviorSubject<Bool>(value: false)
var disposeBag = DisposeBag()
var progressObserver = BehaviorRelay<(time: CMTime, duration: CMTime, progress: Float)>.init(value: (time: .zero, duration: .zero, progress: 0))
func setPlaylist(list: [AudioTrack]) {
self.playlist = list
self.playlist.accept(list)
//
do {
@ -95,15 +96,17 @@ class AudioManager {
let playerItem: AVPlayerItem = AVPlayerItem.init(asset: CachingAVURLAsset(url: url))
self.disposeBag = DisposeBag.init()
player?.pause()
player = AVPlayer(playerItem: playerItem)
player?.play()
player?.volume = 1
self.currentTrack = track
self.currentTrack.accept(track)
NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track)
// updateLockScreenDisplay(track: track)
player?.rx.observe(AVPlayer.TimeControlStatus.self, "timeControlStatus")
.map { $0 == .playing }
@ -118,6 +121,20 @@ class AudioManager {
})
.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 {
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() {
guard let playlist = playlist else { return }
let playlist = playlist.value
switch playerShuffleType {
case .random:
@ -164,7 +192,8 @@ class AudioManager {
}
public func previousTrack() {
if currentTrack != nil, let playlist = playlist {
let playlist = playlist.value
if currentTrack.value != nil {
let index = currentTrackIndex - 1
if index >= 0 && index < playlist.count {
playTrack(track: playlist[index])
@ -211,7 +240,7 @@ class AudioManager {
} else {
self.trySeekToChaseTime()
}
if let currentTrack = self.currentTrack {
if let currentTrack = self.currentTrack.value {
self.updateLockScreenDisplay(track: currentTrack)
}
}
@ -277,7 +306,7 @@ class AudioManager {
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, !self.isPlaying() {
if let currentTrack = currentTrack.value, !self.isPlaying() {
self.player?.play()
// updateLockScreenDisplay(track: currentTrack)
return .success
@ -287,7 +316,7 @@ class AudioManager {
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [unowned self] event in
if let currentTrack = currentTrack, self.isPlaying() {
if let currentTrack = currentTrack.value, self.isPlaying() {
self.player?.pause()
// updateLockScreenDisplay(track: currentTrack)
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
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)
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
self?.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack
self?.playerTabBar.audioTrack = AudioManager.sharedInstance.currentTrack.value
self?.playerTabBar.playButton.isSelected = true
}.disposed(by: rx.disposeBag)
@ -208,15 +208,19 @@ class HomeTabBarController: UITabBarController, Navigatable {
output.notiPlayPause.subscribe { [weak self] noti in
self?.playerTabBar.playButton.isSelected = false
}.disposed(by: rx.disposeBag)
output.progress.subscribe { [weak self] progress in
self?.playerTabBar.progressView.progress = progress
}.disposed(by: rx.disposeBag)
// output.progress.subscribe { [weak self] progress in
// self?.playerTabBar.progressView.progress = progress
// }.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) {

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

@ -56,7 +56,7 @@ class HomeViewController: TableViewController, ScrollableNavBar {
if tabbar.customeTabBar.isHidden == true && tabbar.playerTabBar.isHidden == true {
tabbar.showAllBar(true, animated: 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)
}
}
@ -65,7 +65,7 @@ class HomeViewController: TableViewController, ScrollableNavBar {
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)
} else {
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)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTrackArray)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTrackArray)
}.disposed(by: rx.disposeBag)

@ -79,7 +79,7 @@ class SongViewCell: UITableViewCell {
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
} else {
@ -87,7 +87,7 @@ class SongViewCell: UITableViewCell {
}
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
} else {
self?.musicIndicator.state = .stopped

@ -38,6 +38,7 @@ class JournalDetailController: TableViewController {
super.viewWillAppear(animated)
updateNav(navBarBgAlpha: self.currentNavBarBgAlpha)
}
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.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
self?.deselectSelectedRow()
@ -142,12 +160,7 @@ class JournalDetailController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: track, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: track)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: track, audioTracks: audioTracks)
case .journaItem(_): break
}
@ -170,6 +183,8 @@ class JournalDetailController: TableViewController {
guard let self = self else { return }
self.commentToolView.commentCountButton.count = Int(journal.totalCommentReply ?? "")
self.journalCollapsibleHeaderView.journal = journal
self.updateHeader()
@ -202,9 +217,24 @@ class JournalDetailController: TableViewController {
}.disposed(by: rx.disposeBag)
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)
@ -234,7 +264,20 @@ class JournalDetailController: TableViewController {
}.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
.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 ?? ""))
if AudioManager.sharedInstance.currentTrack?.id == audioTrack.id {
if AudioManager.sharedInstance.currentTrack.value?.id == audioTrack.id {
musicIndicator.state = AudioManager.sharedInstance.isPlaying() ? .playing : .paused
} else {
@ -627,7 +627,7 @@ class JournalAudioViewCell: UITableViewCell {
}
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
} else {
self?.musicIndicator.state = .stopped

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

@ -123,13 +123,7 @@ class MineDownloadViewController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}.disposed(by: rx.disposeBag)
@ -146,13 +140,7 @@ class MineDownloadViewController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}.disposed(by: rx.disposeBag)
@ -337,7 +325,7 @@ class MineDownloadViewCell: UITableViewCell {
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
} else {
@ -345,7 +333,7 @@ class MineDownloadViewCell: UITableViewCell {
}
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
} else {
self?.musicIndicator.state = .stopped

@ -11,6 +11,11 @@ import RxCocoa
import RxDataSources
class MineJournalViewController: CollectionViewController {
let headerView: MineSingleHeaderView = {
let headerView = MineSingleHeaderView.init()
return headerView
}()
override func viewDidLoad() {
super.viewDidLoad()
@ -42,6 +47,7 @@ class MineJournalViewController: CollectionViewController {
emptyDataSetDescription = "你还没收藏过期刊!"
view.addSubview(headerView)
}
override func bindViewModel() {
@ -54,7 +60,8 @@ class MineJournalViewController: CollectionViewController {
let input = MineJournalViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
headerRefresh: refresh,
footerRefresh: footerRefreshTrigger,
selection: collectionView.rx.itemSelected.asDriver())
selection: collectionView.rx.itemSelected.asDriver(),
journalSongTrigger: headerView.playAllButton.rx.tap.asDriver())
let output = viewModel.transform(input: input)
@ -73,10 +80,16 @@ class MineJournalViewController: CollectionViewController {
}.disposed(by: rx.disposeBag)
let updateEmptyDataSet = Observable.of(isLoading.mapToVoid().asObservable(), emptyDataSetImageTintColor.mapToVoid(), languageChanged.asObservable()).merge()
updateEmptyDataSet.subscribe(onNext: { [weak self] () in
self?.collectionView.reloadEmptyDataSet()
}).disposed(by: rx.disposeBag)
output.toPlay.subscribe { [weak self] audioTracks in
guard let audioTracks = audioTracks.element,
let audioTrack = audioTracks.first else { return }
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() {
super.viewDidLayoutSubviews()
collectionView.snp.makeConstraints { make in
headerView.snp.remakeConstraints { make in
make.left.equalTo(view)
make.right.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)
}
}
}

@ -16,18 +16,20 @@ class MineJournalViewModel: ViewModel, ViewModelType {
let headerRefresh: Observable<Void>
let footerRefresh: Observable<Void>
let selection: Driver<IndexPath>
let journalSongTrigger: Driver<Void>
}
struct Output {
let items: BehaviorRelay<[MineJournalSection]>
let selection: Driver<IndexPath>
let itemSelected: PublishSubject<Journal>
let toPlay: PublishRelay<[AudioTrack]>
}
let itemSelected = PublishSubject<Journal>()
let items = BehaviorRelay<[MineJournalSection]>.init(value: [])
// let item = BehaviorRelay<Journal?>.init(value: nil)
let toPlay = PublishRelay<[AudioTrack]>.init()
func transform(input: Input) -> Output {
@ -69,11 +71,24 @@ class MineJournalViewModel: ViewModel, ViewModelType {
}.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,
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)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioModels)
AudioManager.sharedInstance.playTrack(track: track)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: track, audioTracks: audioModels)
}.disposed(by: rx.disposeBag)
@ -117,13 +111,7 @@ class MineSingleController: TableViewController {
let playerViewModel = PlayerViewModel.init(track: audioTrack, provider: viewModel.provider)
self?.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}.disposed(by: rx.disposeBag)

@ -45,7 +45,7 @@ class MineViewController: TableViewController {
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)
} else {
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)
self.navigator.show(segue: .player(viewModel: playerViewModel), sender: self, transition: .modal)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//
AudioManager.sharedInstance.setPlaylist(list: audioTracks)
AudioManager.sharedInstance.playTrack(track: audioTrack)
}
AudioManager.sharedInstance.changePlaylistAndPlay(audioTrack: audioTrack, audioTracks: audioTracks)
}.disposed(by: rx.disposeBag)

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

@ -23,6 +23,7 @@ class AudioTrackListViewController: ViewController {
tableView.register(SongViewCell.self
, forCellReuseIdentifier: "SongViewCell")
tableView.separatorColor = .clear
tableView.backgroundColor = .clear
return tableView
}()
@ -48,11 +49,6 @@ class AudioTrackListViewController: ViewController {
view.addSubview(tableView)
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>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: SongViewCell = tableView.dequeueReusableCell(withIdentifier: "SongViewCell", for: indexPath) as! SongViewCell
cell.backgroundColor = .clear
if case let .audioItem(model) = item {
cell.audioTrack = model

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

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

@ -162,25 +162,25 @@ class PlayerViewController: ViewController {
}.disposed(by: rx.disposeBag)
output.progress.subscribe { [weak self] progress in
guard let self = self else { return }
if !self.playerScrollView.playerInfoView.playerSlider.isDrop {
self.playerScrollView.playerInfoView.playerSlider.slider.value = progress
}
}.disposed(by: rx.disposeBag)
output.duration.subscribe { [weak self] duration in
guard let self = self else { return }
self.playerScrollView.playerInfoView.playerSlider.totalSec = duration
}.disposed(by: rx.disposeBag)
output.time.subscribe { [weak self] time in
guard let self = self else { return }
self.playerScrollView.playerInfoView.playerSlider.updateTimeLabel(currentTime: time)
}.disposed(by: rx.disposeBag)
// output.progress.subscribe { [weak self] progress in
// guard let self = self else { return }
//
// if !self.playerScrollView.playerInfoView.playerSlider.isDrop {
// self.playerScrollView.playerInfoView.playerSlider.slider.value = progress
// }
// }.disposed(by: rx.disposeBag)
//
// output.duration.subscribe { [weak self] duration in
// guard let self = self else { return }
//
// self.playerScrollView.playerInfoView.playerSlider.totalSec = duration
// }.disposed(by: rx.disposeBag)
//
// output.time.subscribe { [weak self] time in
// guard let self = self else { return }
//
// self.playerScrollView.playerInfoView.playerSlider.updateTimeLabel(currentTime: time)
// }.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)
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() {

@ -219,11 +219,10 @@ class PlayerViewModel: ViewModel, ViewModelType {
func updateAudioTrack(audioTrack: 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 }) {
// 使 AudioTrack
AudioManager.sharedInstance.playlist?[index] = audioTrack
}
AudioManager.sharedInstance.playlist.accept(playlist)
}

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

@ -59,7 +59,7 @@ class SearchViewController: ViewController, UIScrollViewDelegate {
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)
} else {
collectionView.contentInset = .init(top: 0, left: 0, bottom: BaseDimensions.tabBarHeight, right: 0)

@ -53,6 +53,9 @@ protocol IndieMusicAPI {
func messageList() -> Single<MessageList>
///
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]>
///

@ -36,6 +36,7 @@ enum APIConfig {
case messageList
case collectSongList([String: Any])
case collectJournalSongList([String: Any])
case journalCollectList([String: Any])
case followingList(String, Int, Int)
case followerList(String, Int, Int)
@ -110,6 +111,8 @@ extension APIConfig: TargetType {
return "luoo-user/userMessage/list/"
case .collectSongList:
return "luoo-music/song/collect"
case .collectJournalSongList:
return "luoo-music/journal/collect/song"
case .journalCollectList:
return "luoo-music/journal/collect"
case .followingList(let userId, let page, let size):
@ -166,7 +169,7 @@ extension APIConfig: TargetType {
var method: Moya.Method {
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
case .sendsms, .login, .autoLogin, .editAvatar, .like, .checkVersion, .logout, .sendComment, .feedback, .commentReport:
return .post
@ -180,7 +183,7 @@ extension APIConfig: TargetType {
var parameterEncoding: ParameterEncoding {
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
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:
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
return .requestParameters(parameters: parameters, encoding: parameterEncoding)
@ -238,7 +241,7 @@ extension APIConfig: TargetType {
var headers : [String : String]? {
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 ?? ""]
default:
return nil

@ -215,6 +215,16 @@ extension RestApi {
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]> {
let dic = ["userId": userId,
"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