I am trying to set the local notification sound to my downloaded audio.
But when notification trigger no sound is played. I don't know where i am going wrong. Please have a look on code and let me know my mistake.
func setNotificationWithDate(date: NSDate, onWeekdaysForNotify: [Int], snooze: Bool, soundName: String, title: String, vibrate: Bool) {
let AlarmNotification: UILocalNotification = UILocalNotification()
AlarmNotification.alertBody = title
AlarmNotification.alertAction = "Open App"
AlarmNotification.category = "AlarmCategory"
AlarmNotification.timeZone = NSTimeZone.defaultTimeZone()
let datesForNotification = correctDate(date, onWeekdaysForNotify:onWeekdaysForNotify)
for d in datesForNotification
{
AlarmNotification.fireDate = d
let tracksDic = appDelegate().alarmTrackArray.objectAtIndex(d.dayOfWeek()!)
let sound = tracksDic.valueForKey("t_id") as? String
AlarmNotification.soundName = sound! + ".mp3"
print(AlarmNotification.soundName)
let userInfo = ["date":date,"soundDetail":tracksDic,"title":title ,"vibrate":vibrate]
AlarmNotification.userInfo = userInfo
print(AlarmNotification.soundName)
UIApplication.sharedApplication().scheduleLocalNotification(AlarmNotification)
}
}
Make sure the sound is actually in your app’s bundle, is in the correct format (linear PCM or IMA4—pretty much anywhere that explains how to convert sounds for iOS will tell you how to do that), and is under 30 seconds.
Reference Link :
Choose custom sound for local notifications
Update according to your comment :
Downloaded file will not be in your bundle. so it will not play that sound!!!!
Related
i'm thinking about an iOS alarm app. At the time of the alarm i want to play custom music from Apple Music or another source.
Unfortunately app background operations are really restrictive and notifications only allow bundled music files to be played. Is there any way to achieve my goal by using background tasks or something else?
There're no other choices except using notifications, seealso: Background Execution
Change the sound of notification.
1. Import audio files.
Visit the iPod music library
let mediaquery = MPMediaQuery()
// MPMusicPlayerControllerNowPlayingItemDidChangeNotification
if let musics = mediaquery.items {
for music in musics {
let title = music.valueForProperty(MPMediaItemPropertyTitle) as? String
if let url = music.assetURL {
saveNotificationSound(url,name: title,isLast: music == musics.last)
}
}
}
The important param is assetURL, you can get audio file by it. NOTICE: If the music is download from Apple Music or in the iCloud, it's assertURL is nil.
Use file sharing
How to enable file sharing for my App
2. Cut the audio to 30s and specified format for notification
Because the notification is limited: 1. The duration is not more than 30s; 2. The format is limited, we cut it to m4a.
/**
Cut the duration and convert to m4a, than save it.
- parameter audioPath: Source file path
- parameter startTime: Cut start time
- parameter endTime: Cut end time
- parameter saveDirect: ...
- parameter handler: ...
*/
func cutoffAudio(audioPath: NSURL, startTime: Int64, endTime: Int64, saveDirect:NSURL, handler: (succeed: Bool) -> Void){
let audioAsset = AVURLAsset(URL: audioPath, options: nil)
if let exportSession = AVAssetExportSession(asset: audioAsset, presetName: AVAssetExportPresetAppleM4A){
let startTime = CMTimeMake(startTime, 1)
let stopTime = CMTimeMake(endTime, 1)
exportSession.outputURL = saveDirect
// Output is m4a
exportSession.outputFileType = AVFileTypeAppleM4A
exportSession.timeRange = CMTimeRangeFromTimeToTime(startTime, stopTime)
exportSession.exportAsynchronouslyWithCompletionHandler({
handler(succeed: exportSession.status == .Completed)
})
}
}
3. Set as the sound of notification
Attention: The custom audio files can only be placed in /Library/Sounds in App's Sandbox, than the soundName only needs to provide the file name (including the extension), the audio files are just like in the main bundle.
Here is a demo from github.com/ToFind1991
The code is not compatible with the current Swift version, you need to adjust it.
Since UILocalNotification is deprecated in iOS 10 so I have updated my local notification flow using the UNUserNotification framework.
The app plays custom sound using AVPlayer when the app is in the foreground and it works fine. But in background mode when local notification is triggered, instead of custom sound, a default notification sound is being played.
However, things were working fine in iOS9, using "didReceiveLocalNotification" method app can play custom sound even in background mode.
Update 1:
I'm setting local notification as follows:
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationStringForKey("reminder!", arguments: nil)
content.body = NSString.localizedUserNotificationStringForKey("Reminder body.", arguments: nil)
if let audioUrl == nil {
content.sound = UNNotificationSound.defaultSound()
} else {
let alertSound = NSURL(fileURLWithPath: self.selectedAudioFilePath)
content.sound = UNNotificationSound(named: alertSound.lastPathComponent!)
}
content.userInfo = infoDic as [NSObject : AnyObject]
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
let identifier = "Reminder-\(date)"
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.currentNotificationCenter()
center.addNotificationRequest(request, withCompletionHandler: { (error) in
if error != nil {
print("local notification created successfully.")
}
})
I have already gone through many SO Questions but didn't get the solution.
Any help will be greatly appreciated!
In Swift 4.2 and Xcode 10.1
For local notifications to play sound
let content = UNMutableNotificationContent()
content.title = "Notification Tutorial"
content.subtitle = "from ioscreator.com"
content.body = " Notification triggered"
//Default sound
content.sound = UNNotificationSound.default
//Play custom sound
content.sound = UNNotificationSound(named:UNNotificationSoundName(rawValue: "123.mp3"))
"123.mp3" file needs to be in your Bundle.
Found out that alertSound doesn't have the correct file path hence the content.sound sets nothing. I was saving the recorded file in documents directory and the file path retrieve for real device is different as compared to the simulator.
Setting a sound file which is placed in project bundle did the trick
content.sound = UNNotificationSound(named: "out.caf")
In your push notification package you need to include the custom sound you want to play in the background. This sound needs to be included in your application too.
For example, here is how I create a custom notification package in PHP for iOS. Notice in my notification object I have a sound object.
array("to" => $sDeviceID,
"data" => array($sMessageType => $sMessage,
"customtitle" => $sMessageTitle,
"custombody" => $sMessage,
"customimage" => "$mIcon") ,
"notification" => array("title" => "sMessageTitle",
"text" => $sMessage,
"icon" => "$mIcon",
"sound" => "mySound.mp3"));
Here is how to create a notification using the UserNotifications framework with a custom sound.
If notification is your UNNotificationContent and the name of your custom media file that is in your bundle is a.mp4, just use the following code:
notification.sound = UNNotificationSound(named: "a.mp4")
So, you don't need to use didRecieveLocalNotification.
I understand that
let notificationContent = UNMutableNotificationContent()
...
notificationContent.sound = UNNotificationSound(named: "out.mp3")
is working fine, but:
1) the sound file should not be longer than 30 sec (which is ok, I think)
2) the sound file has to be part of the Bundle.mail files. If it is stored somewhere else the notification will not have access to it, when in background. It works fine in foreground. Apple does not provide permission to access other folders as far as I know.
3) there are restrictions on the sound format. Please refer to Apple guides
4) I'm not sure if sound files in Bundle.main can be changed
programmatically. For example: I wrote a routine which extracts 30 sec of a song stored in apple music and chosen by the user... everything works fine, if the info is stored in a different directory, but only in foreground... once locked or in background there is no access to it. I'm not able to change/overwrite a file in Bundle.main. If that would be possible the problem would be solved ... but I think that would be a security issue. I conclude that alert sounds and music are working fine as long as the above is met.
let notificationContent1 = UNMutableNotificationContent()
notificationContent1.title = "Good morning! Would you like to listen to a morning meditation to start the day fresh ? Or maybe a cool relaxing soundscape? Open the app and browse our collection which is updated daily."
notificationContent1.subtitle = ""
notificationContent1.body = ""
notificationContent1.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "AlarmSound.wav"))
var dateComponents1 = DateComponents()
dateComponents1.hour = 08
dateComponents1.minute = 00
let trigger1 = UNCalendarNotificationTrigger(dateMatching: dateComponents1, repeats: true)
let request1 = UNNotificationRequest(
identifier: UUID().uuidString,
content: notificationContent1,
trigger: trigger1
)
let notificationContent2 = UNMutableNotificationContent()
notificationContent2.title = "Good evening! Hope you had a great day. Would you like to meditate on a particular topic ? Or just some relaxing sleep sounds ? Open the app and pick what you want so you can go to sleep relaxed."
notificationContent2.subtitle = ""
notificationContent2.body = ""
notificationContent2.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "AlarmSound.wav"))
var dateComponents2 = DateComponents()
dateComponents2.hour = 20
dateComponents2.minute = 00
let trigger2 = UNCalendarNotificationTrigger(dateMatching: dateComponents2, repeats: true)
let request2 = UNNotificationRequest(
identifier: UUID().uuidString,
content: notificationContent2,
trigger: trigger2
)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request1, withCompletionHandler: nil)
UNUserNotificationCenter.current().add(request2, withCompletionHandler: nil)
I'm making an iOS app using Swift 3 in which displaying information about the currently playing item on the lock screen and control center would be nice.
Currently, I'm using the following code to attempt to insert this information into the nowPlayingInfo dictionary. I've also included reference to the VideoInfo class that is used in videoBeganPlaying(_:).
class VideoInfo {
var channelName: String
var title: String
}
// ...
var videoInfoNowPlaying: VideoInfo?
// ...
#objc private func videoBeganPlaying(_ notification: NSNotification?) {
// apparently these have to be here in order for this to work... but it doesn't
UIApplication.shared.beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
guard let info = self.videoInfoNowPlaying else { return }
let artwork = MPMediaItemArtwork(boundsSize: .zero, requestHandler:
{ (_) -> UIImage in #imageLiteral(resourceName: "DefaultThumbnail") }) // this is filler
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle: info.title,
MPMediaItemPropertyArtist: info.channelName,
MPMediaItemPropertyArtwork: artwork
]
print("Current title: ", MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyTitle])
}
The function is called when it should be, and the print statement executed, outputting Optional("title"). However, the control center and lockscreen do not update their information. Pause/play, and the skip forward button work, as I set them in viewDidLoad() using MPRemoteCommandCenter.
What's going wrong?
EDIT:
As matt pointed out, AVPlayerViewController makes the MPNowPlayingInfoCenter funky. This was my issue. I should have specified that this is the class I am using, not just AVPlayer.
See:
https://developer.apple.com/reference/avkit/avplayerviewcontroller/1845194-updatesnowplayinginfocenter
It does work, and you don't need all that glop about the first responder and so on, as you can readily prove to yourself by just going ahead and setting the now playing info and nothing else:
So why isn't it working for you? Probably because you are using some sort of player (such as AVPlayerViewController) that sets the now playing info itself in some way, and thus overrides your settings.
If you are using AVPlayerViewController, you can specify that the AVPlayerViewController instance doesn't update the NowPlayingInfoCenter:
playerViewController.updatesNowPlayingInfoCenter = false
This caught me out. Found a helpful post regarding this. https://nowplayingapps.com/how-to-give-more-control-to-your-users-using-mpnowplayinginfocenter/
"In order for the show the nowplayinginfo on the notification screen, you need to add this one last piece of code inside your AppDelegate’s didFinishLaunchingWithOptions function."
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
application.beginReceivingRemoteControlEvents()
}
2021 and I was still experiencing this issue when using AVPlayerViewController. I'm working on an app that plays both podcast and videos. Now playing info center works correctly with podcast but it doesn't show any info for videos being played using AVPlayerViewController even though I'm setting MPNowPlayingInfoCenter.nowPlayingInfo correctly.
Found a solution watching Now Playing and Remote Commands on tvOS
even though I'm not working with tvOS.
Basically, instead of filling the info using MPNowPlayingInfoCenter I set externalMetadata on the AVPlayerItem and it works.
let playerItem = AVPlayerItem(url: data.url)
let title = AVMutableMetadataItem()
title.identifier = .commonIdentifierTitle
title.value = "Title" as NSString
title.extendedLanguageTag = "und"
let artist = AVMutableMetadataItem()
artist.identifier = .commonIdentifierArtist
artist.value = "Artist" as NSString
artist.extendedLanguageTag = "und"
let artwork = AVMutableMetadataItem()
artwork.identifier = .commonIdentifierArtwork
artwork.value = imageData as NSData
artwork.dataType = kCMMetadataBaseDataType_JPEG as String
artwork.extendedLanguageTag = "und"
playerItem.externalMetadata = [title, artist, artwork]
This code updates now playing info correctly.
I am trying to play a video using MPMoviePlayerController for an iOS app in Swift.
My goal is to be able to play system music with something like apple music, then open my app and have the audio mix in, but I want my app to be able to take control of MPNowPlayingInfoCenter.
How can I use AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: .MixWithOthers) while still set the MPNowPlayingInfoCenter?
Google Maps mixes in audio while taking setting MPNowPlayingInfoCenter. Below is how I am trying to set the MPNowPlayingInfoCenter:
func setMeta(){
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
if let player = PlayWorkoutViewController.player{
let coverArt = MPMediaItemArtwork(image: UIImage(named: "AlbumArt")!)
let dict: [String: AnyObject] = [
MPMediaItemPropertyArtwork: coverArt,
MPMediaItemPropertyTitle:workout.title,
MPMediaItemPropertyArtist:"Alex",
MPMediaItemPropertyAlbumTitle:workout.program.title,
MPNowPlayingInfoPropertyPlaybackRate: player.currentPlaybackRate,
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentPlaybackTime,
MPMediaItemPropertyPlaybackDuration: player.playableDuration
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = dict
}
}
The above function works when I am not trying to play outside music with an option (.MixWithOthers) at the same time, but while I am trying to play outside music with the option (.MixWithOthers) the info center does not update.
Edit 1: Just to make things super clear, I already having video playing properly I am trying to play video with other background audio while being able to set MPNowPlayingInfoCenter.
This isn't currently possible in iOS. Even just changing your category options to .MixWithOthers causes your nowPlayingInfo to be ignored.
My guess is iOS only considers non-mixing apps for inclusion in MPNowPlayingInfoCenter, because there is uncertainty as to which app would show up in (e.g.) Control Center if there are multiple mixing apps playing at the same time.
I'd very much like it if iOS used a best-effort approach for choosing the "now playing app", something like this:
If there's a non-mixing app playing, pick that. Else..
If there's only one mixing app playing, pick that. Else..
If there are multiple mixing apps playing, just pick one :) Or pick none, I'm fine with either.
If you'd like this behavior as well, I'd encourage you to file a bug with Apple.
Have you tried implementing your own custom function to update the MPNowPlayingInfoCenter? Recently I was using an AVAudioPlayer to play music and needed to do the updating manually.
This is basically the function I called upon a new song being loaded.
func updateNowPlayingCenter() {
let center = MPNowPlayingInfoCenter.defaultCenter()
if nowPlayingItem == nil {
center.nowPlayingInfo = nil
} else {
var songInfo = [String: AnyObject]()
// Add item to dictionary if it exists
if let artist = nowPlayingItem?.artist {
songInfo[MPMediaItemPropertyArtist] = artist
}
if let title = nowPlayingItem?.title {
songInfo[MPMediaItemPropertyTitle] = title
}
if let albumTitle = nowPlayingItem?.albumTitle {
songInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
}
if let playbackDuration = nowPlayingItem?.playbackDuration {
songInfo[MPMediaItemPropertyPlaybackDuration] = playbackDuration
}
if let artwork = nowPlayingItem?.artwork {
songInfo[MPMediaItemPropertyArtwork] = artwork
}
center.nowPlayingInfo = songInfo
}
}
I am not sure if doing this upon a movie being loaded will override the MPMoviePlayerController, but it seems worth a shot.
Additionally, they have depreciated MPMoviePlayerController and replaced it with AVPlayerViewController, so thats also worth looking into.
Edit: Also I would check to make sure that you are properly receiving remote control events, as this impacts the data being displayed by the info center.
To play the video in swift use this:-
func playVideoEffect() {
let path = NSBundle.mainBundle().pathForResource("egg_grabberAnmi", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
self.moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = moviePlayer {
let screenSize: CGRect = UIScreen.mainScreen().bounds
player.view.frame = CGRect(x: frame.size.width*0.10,y: frame.size.width/2, width:screenSize.width * 0.80, height: screenSize.width * 0.80)
player.view.sizeToFit()
player.scalingMode = MPMovieScalingMode.Fill
player.fullscreen = true
player.controlStyle = MPMovieControlStyle.None
player.movieSourceType = MPMovieSourceType.File
player.play()
self.view?.addSubview(player.view)
var timer = NSTimer.scheduledTimerWithTimeInterval(6.0, target: self, selector: Selector("update"), userInfo: nil, repeats: false)
}
}
// And Use the function to play Video
playVideoEffect()
I have tried the following:
let nowPlaying = MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
However I get back nil everytime I run it with a song playing.
I would like to be able to grab the track title and artist and display it in my app.
You're going about this completely the wrong way. MPNowPlayingInfoCenter has nothing to do with learning what is currently playing. If you want to know what the Music app is currently playing, ask the "iPod music player" (in iOS 8, it is called MPMusicPlayerController.systemMusicPlayer).
try this, if you are writing an iOS app
let musicPlayer = MPMusicPlayerController.systemMusicPlayer
if let nowPlayingItem = musicPlayer.nowPlayingItem {
print(nowPlayingItem.title)
} else {
print("Nothing's playing")
}
This is a modified version of this answer.
Using Swift, you can get the Now Playing info, including title, artist, artwork and app on an iOS device using the following private API:
// Load framework
let bundle = CFBundleCreate(kCFAllocatorDefault, NSURL(fileURLWithPath: "/System/Library/PrivateFrameworks/MediaRemote.framework"))
// Get a Swift function for MRMediaRemoteGetNowPlayingInfo
guard let MRMediaRemoteGetNowPlayingInfoPointer = CFBundleGetFunctionPointerForName(bundle, "MRMediaRemoteGetNowPlayingInfo" as CFString) else { return }
typealias MRMediaRemoteGetNowPlayingInfoFunction = #convention(c) (DispatchQueue, #escaping ([String: Any]) -> Void) -> Void
let MRMediaRemoteGetNowPlayingInfo = unsafeBitCast(MRMediaRemoteGetNowPlayingInfoPointer, to: MRMediaRemoteGetNowPlayingInfoFunction.self)
// Get song info
MRMediaRemoteGetNowPlayingInfo(DispatchQueue.main, { (information) in
let bundleInfo = Dynamic._MRNowPlayingClientProtobuf.initWithData(information["kMRMediaRemoteNowPlayingInfoClientPropertiesData"])
print("\(information["kMRMediaRemoteNowPlayingInfoTitle"] as! String) by \(information["kMRMediaRemoteNowPlayingInfoArtist"] as! String) playing on \(bundleInfo.displayName.asString!)")
})
Returns SONG by ARTIST playing on APP.
Note this uses the Dynamic package to easily execute private headers.
This cannot be used in an App Store app due to the use of private API.
This is not an API to get the current playing item information from Music or another app, but to tell the system that your app is currently playing something and give it the information needed to display it on lock screen.
So basically what you're trying to do won't work as you expect it.
Did you set them?
var audioPlayer:MPMoviePlayerController=MPMoviePlayerController()
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = [
MPMediaItemPropertyAlbumTitle: "Album Title",
MPMediaItemPropertyTitle: "Title",
MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentPlaybackTime,
MPMediaItemPropertyPlaybackDuration: audioPlayer.duration]
The now playing info center supports the following media item property keys:
MPMediaItemPropertyAlbumTitle
MPMediaItemPropertyAlbumTrackCount
MPMediaItemPropertyAlbumTrackNumber
MPMediaItemPropertyArtist
MPMediaItemPropertyArtwork
MPMediaItemPropertyComposer
MPMediaItemPropertyDiscCount
MPMediaItemPropertyDiscNumber
MPMediaItemPropertyGenre
MPMediaItemPropertyPersistentID
MPMediaItemPropertyPlaybackDuration
MPMediaItemPropertyTitle