iOS 媒体播放控件通知
Posted
技术标签:
【中文标题】iOS 媒体播放控件通知【英文标题】:iOS media playback controls notification 【发布时间】:2020-05-10 05:00:18 【问题描述】:我是 ios 新手,正在使用 Flutter 开发跨平台应用程序。我正在尝试从网络 URL 播放音频,我发现它可以使用 AVPlayer
来完成。当应用程序在前台和后台时播放音频,但我可以像这样显示媒体播放控件:。
我使用let mediaController = MPMusicPlayerController.applicationMusicPlayer
然后调用self.mediaController.beginGeneratingPlaybackNotifications()
,同时提供播放信息MPNowPlayingInfoCenter.default().nowPlayingInfo = mediaInfo
并在self.registerCommands()
方法中设置远程指挥中心的目标。
我做了很多研究,但没有找到问题所在,正如我之前所说,我是 ios 新手。
AppDelegate
import UIKit
import Flutter
import AVFoundation
import MediaPlayer
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate
static let CHANNEL = "APP_CHANNEL"
let mPlayer = AudioPlayer()
let mediaController = MPMusicPlayerController.applicationMusicPlayer
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool
self.requestNotificationPermission(application: application)
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let mainChannel = FlutterMethodChannel(name: AppDelegate.CHANNEL,
binaryMessenger: controller.binaryMessenger)
mainChannel.setMethodCallHandler(
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch(call.method)
case "getSharedContainerPath":
let path = Utils.getSharedContainerPath()
result(path)
break
case "saveSelectedCity":
let city = call.arguments as! String
Utils.saveCityToUserDefaults(city: city)
result(true)
break
case "playSurah":
let number = call.arguments as! Int
self.initAudioPlayer()
self.mPlayer.toggle(num: number)
result(true)
break
default:
result(FlutterMethodNotImplemented)
return
)
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
func initAudioPlayer()
self.mediaController.beginGeneratingPlaybackNotifications()
self.mPlayer.initPlayer(object: self)
self.registerCommands()
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
nc.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: nil)
func requestNotificationPermission(application: UIApplication)
if #available(iOS 10, *)
// iOS 10 support
//create the notificationCenter
let center = UNUserNotificationCenter.current()
center.delegate = self as UNUserNotificationCenterDelegate
// set the type as sound or badge
center.requestAuthorization(options: [.sound,.alert,.badge]) (granted, error) in
if granted
print("Notification Enable Successfully")
else
print("Some Error Occure")
application.registerForRemoteNotifications()
else if #available(iOS 9, *)
// iOS 9 support
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
else if #available(iOS 8, *)
// iOS 8 support
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
else // iOS 7 support
application.registerForRemoteNotifications(matching: [.badge, .sound, .alert])
func registerCommands()
let command = MPRemoteCommandCenter.shared()
command.playCommand.isEnabled = true;
command.playCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.play()
return .success
command.pauseCommand.isEnabled = true;
command.pauseCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.pause()
return .success
command.togglePlayPauseCommand.isEnabled = true;
command.togglePlayPauseCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.toggle(num: self.mPlayer.index)
return .success
command.nextTrackCommand.isEnabled = true;
command.nextTrackCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.playNext()
return .success
command.previousTrackCommand.isEnabled = true;
command.previousTrackCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.playPrev()
return .success
command.stopCommand.isEnabled = true;
command.stopCommand.addTarget (_) -> MPRemoteCommandHandlerStatus in
self.mPlayer.stop()
return .success
// [notificationCenter addObserver: self
// selector: @selector (handle_NowPlayingItemChanged:)
// name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
// object: musicPlayer];
//
// [notificationCenter addObserver: self
// selector: @selector (handle_PlaybackStateChanged:)
// name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
// object: musicPlayer];
//
// [notificationCenter addObserver: self
// selector: @selector (handle_VolumeChanged:)
// name: MPMusicPlayerControllerVolumeDidChangeNotification
// object: musicPlayer];
func destroyPlayer()
self.mPlayer.stop()
let nc = NotificationCenter.default
nc.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
nc.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
self.mediaController.endGeneratingPlaybackNotifications()
let command = MPRemoteCommandCenter.shared()
command.playCommand.isEnabled = false;
command.pauseCommand.isEnabled = false;
command.togglePlayPauseCommand.isEnabled = false;
command.nextTrackCommand.isEnabled = false;
command.previousTrackCommand.isEnabled = false;
command.stopCommand.isEnabled = false;
// override func applicationDidReceiveMemoryWarning(_ application: UIApplication)
// self.destroyPlayer()
//
override func applicationWillTerminate(_ application: UIApplication)
self.destroyPlayer()
@objc func playerDidFinishPlaying(note: NSNotification)
self.mPlayer.playNext()
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
// Only handle observations for the playerItemContext
guard context == &mPlayer.playerItemContext else
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
if keyPath == #keyPath(AVPlayerItem.status)
let status: AVPlayerItem.Status
if let statusNumber = change?[.newKey] as? NSNumber
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
else
status = .unknown
// Switch over status value
switch status
case .readyToPlay:
self.mPlayer.updateMediaInfo()
break
// Player item is ready to play.
case .failed: break
// Player item failed. See error.
case .unknown: break
// Player item is not yet ready.
@unknown default:
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
else if keyPath == #keyPath(AVPlayer.timeControlStatus)
if object is AVPlayer
if (object as? AVPlayer) != nil
self.mPlayer.updateMediaInfo()
else
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
@objc func handleInterruption(notification: Notification)
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else
return
// Switch over the interruption type.
switch type
case .began:
// An interruption began. Update the UI as needed.
self.mPlayer.pause()
break
case .ended:
// An interruption ended. Resume playback, if appropriate.
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else return
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume)
// Interruption ended. Playback should resume.
self.mPlayer.play()
else
// Interruption ended. Playback should not resume.
default: ()
音频播放器类
//
// AudioPlayer.swift
// Runner
import Foundation
import AVFoundation
import MediaPlayer
class AudioPlayer
private var player: AVPlayer?
var index: Int = 0
private var object: NSObject!
// Key-value observing context
var playerItemContext = 0
private var mediaInfo = [String : Any]()
func initPlayer(object: NSObject)
self.object = object
do
if #available(iOS 10.0, *)
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [.mixWithOthers, .allowAirPlay])
try AVAudioSession.sharedInstance().setActive(false)
else
// Fallback on earlier versions
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .mixWithOthers)
catch
print(error)
func startPlayer()
do
try AVAudioSession.sharedInstance().setActive(true)
catch
print(error)
self.mediaInfo[MPMediaItemPropertyTitle] = ""
self.mediaInfo[MPMediaItemPropertyArtist] = ""
updateMediaInfo()
let url = getUrl()
let playerItem = AVPlayerItem(url: url!)
playerItem.addObserver(self.object, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: &playerItemContext)
if self.player == nil
self.player = AVPlayer(playerItem: playerItem)
else
self.player?.replaceCurrentItem(with: playerItem)
self.player?.addObserver(self.object, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options: [.new, .old], context: &playerItemContext)
if let p = self.player
p.play()
getMetadata(for: url!, completionHandler: (metadata) in
self.mediaInfo[MPMediaItemPropertyTitle] = metadata?["title"]
self.mediaInfo[MPMediaItemPropertyArtist] = metadata!["artist"]
self.mediaInfo[MPMediaItemPropertyPlaybackDuration] = playerItem.asset.duration.seconds
self.updateMediaInfo()
)
func toggle(num: Int)
if self.index == num
if let p = self.player
if(p.isPlaying)
p.pause()
else
p.play()
self.updateMediaInfo()
else
self.index = num
startPlayer()
func pause()
if let p = self.player
if(p.isPlaying)
p.pause()
self.updateMediaInfo()
func play()
if let p = self.player
if(!p.isPlaying )
p.play()
self.updateMediaInfo()
func playNext()
if self.index + 1 <= 114
self.index += 1
else
self.index = 1
self.startPlayer()
func playPrev()
if self.index - 1 >= 1
self.index -= 1
else
self.index = 114
self.startPlayer()
func stop()
if let p = self.player
p.pause()
self.player?.replaceCurrentItem(with: nil)
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
func getUrl() -> URL?
return URL(string: String(format: Utils.QURAN_AUDIO, self.index))
func updateMediaInfo()
mediaInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate
mediaInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime().seconds
if #available(iOS 10.0, *)
mediaInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.audio.rawValue)
MPNowPlayingInfoCenter.default().nowPlayingInfo = mediaInfo
func getMetadata(for url: URL, completionHandler: @escaping (_ metadata: [String : String]?) -> ())
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) (data, response, error) in
guard error == nil,
let res1 = response as? HTTPURLResponse,
let contentLength = res1.allHeaderFields["Content-Length"] as? String else
completionHandler(nil)
return
do
var req = URLRequest(url: url)
req.setValue("bytes=\(UInt64(contentLength)! - 128)-", forHTTPHeaderField: "Range")
let data = try NSURLConnection.sendSynchronousRequest(req, returning: nil)
let titleBytes = data.subdata(in: Range<Int>(NSRange(location: 3, length: 29))!)
.filter (data) -> Bool in
data != 0
let artistBytes = data.subdata(in: Range<Int>(NSRange(location: 33, length: 29))!)
.filter (data) -> Bool in
data != 0
let title = String(data: titleBytes, encoding: String.Encoding.utf8)
let artist = String(data: artistBytes, encoding: String.Encoding.utf8)
completionHandler(["title": title!, "artist": artist!])
catch
completionHandler(nil)
task.resume()
extension AVPlayer
var isPlaying: Bool
if #available(iOS 10.0, *)
return timeControlStatus.rawValue == TimeControlStatus.playing.rawValue
return rate != 0 && error == nil
【问题讨论】:
谁能帮忙 目前还不清楚是什么问题。 “显示媒体播放控件”是什么意思? 我想在主屏幕上获得媒体播放控件,例如:miro.medium.com/max/378/1*wLeFryy1Cqb-Yj_V8Agbiw.png 但它不会显示 这个-***.com/a/70240063/4833705 【参考方案1】:来自评论:
我没有真机,我用的是 iPhone 11 pro max 模拟器
这就是问题所在。只能在设备上测试此功能。模拟器不是许多 iOS 功能/行为的可靠指南,这就是其中之一。如果没有设备,您就无法证明您的代码是否按预期工作。
【讨论】:
哦,谢谢,我不知道,我已经被这个问题困扰了好几天了,以为我的代码有问题 您的代码可能有问题。我的意思是,没有设备,你就没有任何证据。【参考方案2】:如果我没听错的话,NowPlayingInfo 不会显示您的 MediaInfo(标题等)。
这是因为当前 iOS 在启用 .mixWithOthers
选项的情况下忽略来自 AVAudioSessions 的 NowPlayingInfo。
我确实使用您的代码设置了一个小测试项目。使用.mixWithOthers
选项,我可以重现您的问题。删除此选项后,NowPlayingInfoCenter 按预期工作。
还有一点:
尝试设置 AVAudioSession 类别时,我总是收到错误Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"
。
这是因为类别.playback
不允许设置.allowsAirPlay
选项。 (https://developer.apple.com/documentation/avfoundation/avaudiosession/categoryoptions/1771736-allowairplay)
【讨论】:
感谢您的响应,我删除了 .mixWithOthers 和 .allowsAirPlay ,但仍然没有获得媒体播放控件。我还创建了一个新的 swift 项目并将播放器代码复制到其中,但同样的事情,没有播放控件出现。难道我做错了什么 ?你能分享你创建的测试项目吗? thnx - PS:我没有真机,我用的是 iPhone 11 pro max 模拟器 此音频控件不会出现在模拟器上,您需要在真实设备上运行。以上是关于iOS 媒体播放控件通知的主要内容,如果未能解决你的问题,请参考以下文章
在 iOS 10 中使用远程通知(富媒体推送通知)在通知栏中显示图像