620 lines
22 KiB
Swift
620 lines
22 KiB
Swift
//
|
||
// MPMediaCenterManager.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/4/11.
|
||
//
|
||
|
||
import Foundation
|
||
import AVFoundation
|
||
import MediaPlayer
|
||
///播放器播放事件类型
|
||
enum MPSideA_PlayerPlayActionType:Int {
|
||
///正常播放
|
||
case Normal = 0
|
||
///倒计时播放(用户添加倒计时)
|
||
case CountTime = 1
|
||
}
|
||
///播放器播放状态
|
||
enum MPSideA_PlayerStateType:Int {
|
||
///未播放
|
||
case Null = 0
|
||
///播放中
|
||
case Playing = 1
|
||
///暂停
|
||
case Pause = 2
|
||
}
|
||
///倒计时状态
|
||
enum MPSideA_TimerType:Int {
|
||
///未启动
|
||
case UnActivity = 0
|
||
///运行中
|
||
case Playing = 1
|
||
///暂停中
|
||
case Suspend = 2
|
||
}
|
||
///倒计时规格等级
|
||
enum MPSideA_CountTimerLevel:Int, CaseIterable {
|
||
case OFF = 0
|
||
case _10 = 1
|
||
case _20 = 2
|
||
case _30 = 3
|
||
case _60 = 4
|
||
case _90 = 5
|
||
///标题
|
||
var title:String{
|
||
switch self {
|
||
case .OFF:
|
||
return "OFF"
|
||
case ._10:
|
||
return "10"
|
||
case ._20:
|
||
return "20"
|
||
case ._30:
|
||
return "30"
|
||
case ._60:
|
||
return "60"
|
||
case ._90:
|
||
return "90"
|
||
}
|
||
}
|
||
///实际数值(分)
|
||
var mins:Int{
|
||
switch self {
|
||
case .OFF:
|
||
return 0
|
||
case ._10:
|
||
return 10
|
||
case ._20:
|
||
return 20
|
||
case ._30:
|
||
return 30
|
||
case ._60:
|
||
return 60
|
||
case ._90:
|
||
return 90
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
///多媒体控制器(播放控制器,倒计时控制器,麦克风管理器)
|
||
class MPSideA_MediaCenterManager {
|
||
///控制器单例
|
||
static let shared = MPSideA_MediaCenterManager()
|
||
//MARK: - 各项工具
|
||
//音乐播放器
|
||
private var player:AVPlayer?
|
||
//远程控制中心
|
||
private var center:MPRemoteCommandCenter?
|
||
//GCD倒计时器
|
||
private var countTimer: DispatchSourceTimer?
|
||
//监听器(需要获取麦克风权限),通过监听器实时获取周遭分贝
|
||
private var monitor:AVAudioRecorder?
|
||
//监听器设置参数字典
|
||
private var monitorSetingsDic:[String : Any]?
|
||
//GCD监听计时器
|
||
private var monitorTimer: DispatchSourceTimer?
|
||
|
||
//MARK: - 实体与状态值
|
||
///音乐实体
|
||
private var music:MPSideA_MusicModel?
|
||
///获取音乐实体
|
||
func getMusic() -> MPSideA_MusicModel?{
|
||
return music
|
||
}
|
||
///更改音乐实体
|
||
func setMusic(_ music:MPSideA_MusicModel?) {
|
||
self.music = music
|
||
}
|
||
///播放器播放方法
|
||
private var playActionType:MPSideA_PlayerPlayActionType = .Normal
|
||
///播放器播放状态(默认未播放)
|
||
private var playerState:MPSideA_PlayerStateType = .Null
|
||
///获取播放器播放状态
|
||
func getPlayerState() -> MPSideA_PlayerStateType {
|
||
return playerState
|
||
}
|
||
///倒计时运行状态(默认未启动)
|
||
private var countTimeType:MPSideA_TimerType = .UnActivity
|
||
///倒计时规格 (默认为OFF)
|
||
private var countTimerLevel:MPSideA_CountTimerLevel = .OFF
|
||
///获取倒计时规格值
|
||
func getCountTimerLevel() -> MPSideA_CountTimerLevel {
|
||
return countTimerLevel
|
||
}
|
||
///监听器计时状态
|
||
private var monitorTimerType:MPSideA_TimerType = .UnActivity
|
||
///监听器是否活跃
|
||
private var isMonitorActivity:Bool = false
|
||
//隐藏管理器初始化方法
|
||
private init() {
|
||
//初始化时获取上次运行app时存入的最后一首音乐(保存的内容为音乐实体的identifier,具备唯一性)
|
||
let lastTitle = UserDefaults.standard.string(forKey: "Last") ?? ""
|
||
let last = MPSideA_MusicModel.fetch(.init(format: "identifier==%@", lastTitle)).first
|
||
//更新音乐实体(可选性)
|
||
music = last
|
||
// 添加观察者,监听播放结束事件
|
||
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
|
||
|
||
//初始化监听器字典并添加设置参数
|
||
monitorSetingsDic =
|
||
[
|
||
//音频格式
|
||
AVFormatIDKey:NSNumber(value: kAudioFormatLinearPCM),
|
||
//录音的声道数,立体声为双声道
|
||
AVNumberOfChannelsKey: 2,
|
||
//录音质量
|
||
AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
|
||
//采样位数
|
||
AVLinearPCMBitDepthKey:NSNumber(value:16),
|
||
AVEncoderBitRateKey : 320000,
|
||
//录音器每秒采集的录音样本数
|
||
AVSampleRateKey : 44100.0
|
||
]
|
||
}
|
||
|
||
//管理器销毁时
|
||
deinit {
|
||
//解除监听
|
||
NotificationCenter.default.removeObserver(self)
|
||
//释放播放器
|
||
player = nil
|
||
//释放倒计时器
|
||
countTimer?.cancel()
|
||
countTimer = nil
|
||
//释放监听器
|
||
monitor = nil
|
||
monitorTimer?.cancel()
|
||
monitorTimer = nil
|
||
}
|
||
//MARK: - 播放器与倒计时器
|
||
/// 启动播放器
|
||
/// - Parameters:
|
||
/// - music: 音乐实体
|
||
/// - actionType: 播放方法(正常播放/倒计时播发)
|
||
/// - countLevel: 如果倒计时播放则填入倒计时规格
|
||
func playerStart(_ music:MPSideA_MusicModel, actionType:MPSideA_PlayerPlayActionType, countLevel: MPSideA_CountTimerLevel = .OFF) {
|
||
//检索倒计时器状态
|
||
switch countTimeType {
|
||
case .UnActivity://未启动计时器
|
||
break
|
||
case .Playing://计时器运行中
|
||
if countTimer != nil {
|
||
//倒计时器具备实例,处于rsume中,可以直接销毁
|
||
countTimer!.cancel()
|
||
countTimer = nil
|
||
}
|
||
case .Suspend://计时器暂停中,销毁计时器
|
||
if countTimer != nil {
|
||
//倒计时器具备实例,处于suspend中,可以直接销毁
|
||
countTimer!.resume()
|
||
countTimer!.cancel()
|
||
countTimer = nil
|
||
}
|
||
}
|
||
//检索当前音乐播放器状态
|
||
switch playerState {
|
||
case .Null://未播放,跳过
|
||
break
|
||
case .Playing://播放中
|
||
//销毁播放器
|
||
if player != nil {
|
||
player!.pause()
|
||
player = nil
|
||
}
|
||
//发送停止音乐播放通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_stop_music)
|
||
case .Pause://暂停中
|
||
//销毁播放器
|
||
if player != nil {
|
||
player = nil
|
||
}
|
||
//发送停止音乐播放通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_stop_music)
|
||
}
|
||
//更新播放方法
|
||
playActionType = actionType
|
||
//检索播放方法
|
||
switch playActionType {
|
||
case .Normal://正常播放
|
||
playMusic(music)
|
||
case .CountTime://倒计时播放
|
||
//更新倒计时规格
|
||
countTimerLevel = countLevel
|
||
countTimerStart(Double(countLevel.mins*60), music: music)
|
||
}
|
||
}
|
||
//倒计时播放时触发
|
||
private func countTimerStart(_ totalTimes:TimeInterval, music:MPSideA_MusicModel) {
|
||
// //检索倒计时器状态
|
||
// switch countTimeType {
|
||
// case .UnActivity://未启动计时器
|
||
// break
|
||
// case .Playing://计时器运行中
|
||
// if countTimer != nil {
|
||
// //倒计时器具备实例,处于rsume中,可以直接销毁
|
||
// countTimer!.cancel()
|
||
// countTimer = nil
|
||
// }
|
||
// case .Suspend://计时器暂停中,销毁计时器
|
||
// if countTimer != nil {
|
||
// //倒计时器具备实例,处于suspend中,可以直接销毁
|
||
// countTimer!.resume()
|
||
// countTimer!.cancel()
|
||
// countTimer = nil
|
||
// }
|
||
// }
|
||
//创建倒计时器队列
|
||
let queue = DispatchQueue(label: "com.MPCountTimer.queue")
|
||
//创建倒计时器
|
||
countTimer = DispatchSource.makeTimerSource(queue: queue)
|
||
//设置计时器的起始时间以及触发事件频率为一秒一次
|
||
countTimer!.schedule(deadline: .now(), repeating: .seconds(1))
|
||
//将总时间值赋予times
|
||
var times = totalTimes
|
||
//计时器设置触发事件
|
||
countTimer!.setEventHandler(handler: {
|
||
[weak self] in
|
||
//判断时间值是否归零
|
||
if times > 0 {
|
||
//未归0,倒计时-1
|
||
times -= 1
|
||
//打印倒计时
|
||
print(setTimesToMinSeconds(times))
|
||
//发布时间值变化通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_time_times, object: times)
|
||
}else {
|
||
//执行结束事件
|
||
self?.playerStop()
|
||
}
|
||
})
|
||
//启动倒计时器
|
||
countTimeType = .Playing
|
||
countTimer!.resume()
|
||
print("The CountTimer has started.")
|
||
//调用音乐播放器
|
||
playMusic(music)
|
||
}
|
||
|
||
//启动播放器
|
||
private func playMusic(_ music:MPSideA_MusicModel) {
|
||
//根据音乐实体类型
|
||
self.music = music
|
||
player_play(self.music!.isLocal ? musicLocal():musicDocument())
|
||
}
|
||
//音乐为本地文件
|
||
private func musicLocal() -> URL {
|
||
//本地文件,mp3格式
|
||
let url:URL = .init(fileURLWithPath: Bundle.main.path(forResource: music!.path, ofType: "mp3")!)
|
||
return url
|
||
}
|
||
//音乐为沙盒文件
|
||
private func musicDocument() -> URL {
|
||
var url:URL!
|
||
//用户上传文件,文件路径设置为沙盒
|
||
let directory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
|
||
//获取沙盒中的数据
|
||
let vedioUrl = URL(fileURLWithPath:URL(fileURLWithPath: directory).appendingPathComponent((music!.path)).path)
|
||
//检索路径是否安全授权
|
||
let authozied = vedioUrl.startAccessingSecurityScopedResource()
|
||
if authozied == true {
|
||
//允许安全访问路径,调出文件协调器解码并获取真实文件地址
|
||
let fileCoordinator = NSFileCoordinator()
|
||
fileCoordinator.coordinate(readingItemAt: vedioUrl, options: .withoutChanges, error: nil) { (newUrl) in
|
||
url = newUrl
|
||
}
|
||
}else {
|
||
//未能获取安全授权,强制获取
|
||
url = vedioUrl
|
||
}
|
||
//停止安全访问权限
|
||
vedioUrl.stopAccessingSecurityScopedResource()
|
||
return url
|
||
}
|
||
//播放音乐
|
||
private func player_play(_ url:URL) {
|
||
//检索真实路径对应的文件是否存在
|
||
if FileManager.default.fileExists(atPath: url.path) == true {
|
||
//当前路径的文件正式存在
|
||
//初始化播放器
|
||
let playerItem = AVPlayerItem(url: url)
|
||
//创建AVPlayer
|
||
player = AVPlayer(playerItem: playerItem)
|
||
//正式播放
|
||
player?.play()
|
||
playerState = .Playing
|
||
//启动远程中心并配置相关内容
|
||
setCommandCenter(music!)
|
||
//发送播放器启动通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_play_music)
|
||
//更新最后一次播放内容
|
||
music!.lastTime = Date().timeZone()
|
||
MPSideA_MusicModel.save()
|
||
UserDefaults.standard.set(music!.identifier, forKey: "Last")
|
||
}else {
|
||
//文件不存在,通知用户删除该音乐实体
|
||
print("Couldn't find the file.")
|
||
playerState = .Null
|
||
music = nil
|
||
//发送音乐缺失通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_null_music)
|
||
}
|
||
}
|
||
///暂停播放器
|
||
func playerPause() {
|
||
//检索播放状态,是否进行中
|
||
guard playerState == .Playing, let player = player else {
|
||
//未处于播放中
|
||
print("Player is not in playing")
|
||
return
|
||
}
|
||
//处于播放中,检索播放方法
|
||
switch playActionType {
|
||
case .Normal://正常播放
|
||
player_pause(player)
|
||
case .CountTime://倒计时播放
|
||
//检索倒计时器状态,是否进行中
|
||
guard countTimeType == .Playing, let countTimer = countTimer else {
|
||
//倒计时器未运行
|
||
print("CountTimer is not playing")
|
||
return
|
||
}
|
||
//倒计时运行中,暂停倒计时
|
||
countTimer.suspend()
|
||
countTimeType = .Suspend
|
||
//暂停播放
|
||
player_pause(player)
|
||
}
|
||
}
|
||
//暂停播放
|
||
private func player_pause(_ player:AVPlayer) {
|
||
//暂停播放器
|
||
player.pause()
|
||
//切换播放器状态
|
||
playerState = .Pause
|
||
//发布音乐暂停公告
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_pause_music)
|
||
}
|
||
///继续播放器
|
||
func playerResume() {
|
||
//检索播放状态,是否暂停中
|
||
guard playerState == .Pause, let player = player else {
|
||
//未处于暂停中
|
||
print("Player is not paused")
|
||
return
|
||
}
|
||
//处于暂停中,检索播放方法
|
||
switch playActionType {
|
||
case .Normal://正常播放
|
||
player_resume(player)
|
||
case .CountTime://倒计时播放
|
||
//检索倒计时器状态,是否暂停中
|
||
guard countTimeType == .Suspend, let countTimer = countTimer else {
|
||
//倒计时器未暂停
|
||
print("CountTimer is not paused")
|
||
return
|
||
}
|
||
//倒计时暂停中,运行倒计时
|
||
countTimer.resume()
|
||
countTimeType = .Playing
|
||
//继续播放
|
||
player_resume(player)
|
||
}
|
||
}
|
||
//继续播放
|
||
private func player_resume(_ player:AVPlayer) {
|
||
//继续播放器
|
||
player.play()
|
||
//切换播放器状态
|
||
playerState = .Playing
|
||
//发布音乐继续公告
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_resume_music)
|
||
}
|
||
///停止播放器
|
||
func playerStop() {
|
||
//检索播放状态,是否已启动
|
||
guard playerState != .Null, let player = player else {
|
||
//未启动
|
||
print("Player is not started")
|
||
return
|
||
}
|
||
//处于启动中,检索播放方法
|
||
switch playActionType {
|
||
case .Normal://正常播放
|
||
player_stop(player)
|
||
case .CountTime://倒计时播放
|
||
//检索倒计时器状态,是否已启动
|
||
guard countTimeType != .UnActivity, let countTimer = countTimer else {
|
||
//倒计时器未启动
|
||
print("CountTimer is not started")
|
||
return
|
||
}
|
||
//倒计时器已启动,终止倒计时
|
||
if countTimeType == .Suspend {
|
||
countTimer.resume()
|
||
}
|
||
countTimer.cancel()
|
||
self.countTimer = nil
|
||
//当计时器终止后,将计时等级回正为off
|
||
countTimerLevel = .OFF
|
||
//停止音乐播放
|
||
player_stop(player)
|
||
}
|
||
}
|
||
//停止播放
|
||
private func player_stop(_ player:AVPlayer) {
|
||
player.pause()
|
||
self.player = nil
|
||
playerState = .Null
|
||
//回正播放类型
|
||
playActionType = .Normal
|
||
//发送停止音乐播放通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_stop_music)
|
||
}
|
||
//播放器停止
|
||
@objc private func playerDidFinishPlaying() {
|
||
guard playerState == .Playing, let player = player else {
|
||
return
|
||
}
|
||
// 重置播放时间到开始
|
||
player.seek(to: CMTime.zero)
|
||
// 继续播放
|
||
player.play()
|
||
}
|
||
//MARK: - 远程中心
|
||
private func setCommandCenter(_ music:MPSideA_MusicModel) {
|
||
// 实例化远程控制中心
|
||
center = MPRemoteCommandCenter.shared()
|
||
|
||
//设置控制中心各项操作
|
||
//播放
|
||
center!.playCommand.addTarget(handler: { [weak self] (event) in
|
||
guard let self = self else { return .noActionableNowPlayingItem}
|
||
if self.music != nil {
|
||
return .success
|
||
}else {
|
||
return .noActionableNowPlayingItem
|
||
}
|
||
})
|
||
//暂停
|
||
center!.pauseCommand.addTarget(handler: { [weak self] (event) in
|
||
guard let self = self else { return .noActionableNowPlayingItem}
|
||
if self.music != nil {
|
||
|
||
return .success
|
||
}else {
|
||
return .noActionableNowPlayingItem
|
||
}
|
||
})
|
||
//设置info字典信息
|
||
var info = [String:Any]()
|
||
//展示标题
|
||
info[MPMediaItemPropertyTitle] = music.title ?? ""
|
||
//设置艺术家
|
||
// info[MPMediaItemPropertyArtist] = ""
|
||
//设置专辑
|
||
// info[MPMediaItemPropertyAlbumTitle] = ""
|
||
//设置歌曲时长
|
||
// info[MPMediaItemPropertyPlaybackDuration] = 0
|
||
//设置歌曲封面
|
||
if let image = UIImage(data: music.cover) {
|
||
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { size in
|
||
return image
|
||
})
|
||
}
|
||
//更新远程中心
|
||
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
|
||
}
|
||
//MARK: - 麦克风监听器
|
||
///开启监听器
|
||
func openMonitor(_ decibels:Double) {
|
||
//优先检索倒计时器和播放器状态
|
||
if countTimeType != .UnActivity || playerState != .Null {
|
||
//有一项处于启动中,终止该事件
|
||
playerStop()
|
||
}
|
||
//检索监听器计时状态
|
||
switch monitorTimerType {
|
||
case .UnActivity://未启动监听器计时
|
||
break
|
||
case .Playing://监听器计时运行中
|
||
if monitorTimer != nil {
|
||
//倒计时器具备实例,处于rsume中,可以直接销毁
|
||
monitorTimer!.cancel()
|
||
monitorTimer = nil
|
||
}
|
||
case .Suspend://监听器计时暂停中
|
||
if monitorTimer != nil {
|
||
//倒计时器具备实例,处于suspend中,可以直接销毁
|
||
monitorTimer!.resume()
|
||
monitorTimer!.cancel()
|
||
monitorTimer = nil
|
||
}
|
||
}
|
||
//创建计时器队列
|
||
let queue = DispatchQueue(label: "com.MPMonitorTimer.queue")
|
||
//实例化监听器计时
|
||
monitorTimer = DispatchSource.makeTimerSource(queue: queue)
|
||
//设置计时器的起始时间以及触发事件频率为一秒一次
|
||
monitorTimer!.schedule(deadline: .now(), repeating: .seconds(1))
|
||
//计时器设置触发事件
|
||
monitorTimer!.setEventHandler(handler: {
|
||
[weak self] in
|
||
guard let self = self else { return }
|
||
//每秒触发一次监听器,获取当前麦克风分贝值
|
||
let currentDecibels = checkDecibels()
|
||
//根据分贝值大小,判断是否启动音乐播放器
|
||
if currentDecibels > decibels {
|
||
guard let music = self.music else {
|
||
return
|
||
}
|
||
//启动十分钟计时器
|
||
playerStart(music, actionType: .CountTime, countLevel: ._10)
|
||
//停止监听器
|
||
stopMonitor()
|
||
}
|
||
})
|
||
//先删除监听器缓存内容,并释放监听器
|
||
monitor?.deleteRecording()
|
||
monitor = nil
|
||
var url:URL?
|
||
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/play"
|
||
//生成空路径
|
||
if #available(iOS 16.0, *) {
|
||
url = URL(filePath: path)
|
||
} else {
|
||
url = URL(fileURLWithPath: path)
|
||
}
|
||
//重新实例化监听器
|
||
do {
|
||
//初始化监听器
|
||
monitor = try AVAudioRecorder(url: url!, settings: monitorSetingsDic!)
|
||
//开启仪表计数功能
|
||
monitor!.isMeteringEnabled = true
|
||
//准备监听
|
||
monitor!.prepareToRecord()
|
||
//开始监听
|
||
monitor!.record()
|
||
//启动监听器计时
|
||
monitorTimerType = .Playing
|
||
isMonitorActivity = true
|
||
monitorTimer!.resume()
|
||
//发送开启监听通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_open_monitor)
|
||
print("The monitor has open.")
|
||
} catch let error {
|
||
print("Monitor initialization failure:\(error.localizedDescription)")
|
||
}
|
||
}
|
||
///终止监听器运行
|
||
func stopMonitor() {
|
||
guard let timer = monitorTimer, let monitor = monitor else { return }
|
||
//停止并销毁计时器和监听器
|
||
if monitorTimerType == .Suspend {
|
||
timer.resume()
|
||
}
|
||
timer.cancel()
|
||
self.monitorTimer = nil
|
||
monitorTimerType = .UnActivity
|
||
isMonitorActivity = false
|
||
monitor.stop()
|
||
self.monitor = nil
|
||
//发送关闭监听通知
|
||
NotificationCenter.notificationKey.post(notificationName: .sideA_stop_monitor)
|
||
print("The monitor has stoped")
|
||
|
||
}
|
||
//检测麦克风音量
|
||
private func checkDecibels() -> Double{
|
||
// 刷新音量数据
|
||
monitor!.updateMeters()
|
||
let power = monitor!.peakPower(forChannel: 0)
|
||
//获取分贝 基本在0-1之间 可能超过1
|
||
let decibels:Double = pow(Double(10), Double(0.05*power))
|
||
print("Current decibels: \(decibels)")
|
||
return decibels
|
||
}
|
||
}
|
||
|